(function(f) {
  if (typeof exports === 'object' && typeof module !== 'undefined') {
    module.exports = f();
  } else if (typeof define === 'function' && define.amd) {
    define([], f);
  } else {
    var g;
    if (typeof window !== 'undefined') {
      g = window;
    } else if (typeof global !== 'undefined') {
      g = global;
    } else if (typeof self !== 'undefined') {
      g = self;
    } else {
      g = this;
    }
    g.adapter = f();
  }
})(function() {
  var define, module, exports;
  return (function() {
    function r(e, n, t) {
      function o(i, f) {
        if (!n[i]) {
          if (!e[i]) {
            var c = 'function' == typeof require && require;
            if (!f && c) return c(i, !0);
            if (u) return u(i, !0);
            var a = new Error("Cannot find module '" + i + "'");
            throw ((a.code = 'MODULE_NOT_FOUND'), a);
          }
          var p = (n[i] = { exports: {} });
          e[i][0].call(
            p.exports,
            function(r) {
              var n = e[i][1][r];
              return o(n || r);
            },
            p,
            p.exports,
            r,
            e,
            n,
            t
          );
        }
        return n[i].exports;
      }
      for (
        var u = 'function' == typeof require && require, i = 0;
        i < t.length;
        i++
      )
        o(t[i]);
      return o;
    }
    return r;
  })()(
    {
      1: [
        function(require, module, exports) {
          /*
           *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
           *
           *  Use of this source code is governed by a BSD-style license
           *  that can be found in the LICENSE file in the root of the source
           *  tree.
           */
          /* eslint-env node */

          'use strict';

          var _adapter_factory = require('./adapter_factory.js');

          var adapter = (0, _adapter_factory.adapterFactory)({
            window: window,
          });
          module.exports = adapter; // this is the difference from adapter_core.
        },
        { './adapter_factory.js': 2 },
      ],
      2: [
        function(require, module, exports) {
          'use strict';

          Object.defineProperty(exports, '__esModule', {
            value: true,
          });
          exports.adapterFactory = adapterFactory;

          var _utils = require('./utils');

          var utils = _interopRequireWildcard(_utils);

          var _chrome_shim = require('./chrome/chrome_shim');

          var chromeShim = _interopRequireWildcard(_chrome_shim);

          var _edge_shim = require('./edge/edge_shim');

          var edgeShim = _interopRequireWildcard(_edge_shim);

          var _firefox_shim = require('./firefox/firefox_shim');

          var firefoxShim = _interopRequireWildcard(_firefox_shim);

          var _safari_shim = require('./safari/safari_shim');

          var safariShim = _interopRequireWildcard(_safari_shim);

          var _common_shim = require('./common_shim');

          var commonShim = _interopRequireWildcard(_common_shim);

          function _interopRequireWildcard(obj) {
            if (obj && obj.__esModule) {
              return obj;
            } else {
              var newObj = {};
              if (obj != null) {
                for (var key in obj) {
                  if (Object.prototype.hasOwnProperty.call(obj, key))
                    newObj[key] = obj[key];
                }
              }
              newObj.default = obj;
              return newObj;
            }
          }

          // Shimming starts here.
          /*
           *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
           *
           *  Use of this source code is governed by a BSD-style license
           *  that can be found in the LICENSE file in the root of the source
           *  tree.
           */
          function adapterFactory() {
            var _ref =
                arguments.length > 0 && arguments[0] !== undefined
                  ? arguments[0]
                  : {},
              window = _ref.window;

            var options =
              arguments.length > 1 && arguments[1] !== undefined
                ? arguments[1]
                : {
                    shimChrome: true,
                    shimFirefox: true,
                    shimEdge: true,
                    shimSafari: true,
                  };

            // Utils.
            var logging = utils.log;
            var browserDetails = utils.detectBrowser(window);

            var adapter = {
              browserDetails: browserDetails,
              commonShim: commonShim,
              extractVersion: utils.extractVersion,
              disableLog: utils.disableLog,
              disableWarnings: utils.disableWarnings,
            };

            // Shim browser if found.
            switch (browserDetails.browser) {
              case 'chrome':
                if (
                  !chromeShim ||
                  !chromeShim.shimPeerConnection ||
                  !options.shimChrome
                ) {
                  logging(
                    'Chrome shim is not included in this adapter release.'
                  );
                  return adapter;
                }
                logging('adapter.js shimming chrome.');
                // Export to the adapter global object visible in the browser.
                adapter.browserShim = chromeShim;

                chromeShim.shimGetUserMedia(window);
                chromeShim.shimMediaStream(window);
                chromeShim.shimPeerConnection(window);
                chromeShim.shimOnTrack(window);
                chromeShim.shimAddTrackRemoveTrack(window);
                chromeShim.shimGetSendersWithDtmf(window);
                chromeShim.shimGetStats(window);
                chromeShim.shimSenderReceiverGetStats(window);
                chromeShim.fixNegotiationNeeded(window);

                commonShim.shimRTCIceCandidate(window);
                commonShim.shimConnectionState(window);
                commonShim.shimMaxMessageSize(window);
                commonShim.shimSendThrowTypeError(window);
                commonShim.removeAllowExtmapMixed(window);
                break;
              case 'firefox':
                if (
                  !firefoxShim ||
                  !firefoxShim.shimPeerConnection ||
                  !options.shimFirefox
                ) {
                  logging(
                    'Firefox shim is not included in this adapter release.'
                  );
                  return adapter;
                }
                logging('adapter.js shimming firefox.');
                // Export to the adapter global object visible in the browser.
                adapter.browserShim = firefoxShim;

                firefoxShim.shimGetUserMedia(window);
                firefoxShim.shimPeerConnection(window);
                firefoxShim.shimOnTrack(window);
                firefoxShim.shimRemoveStream(window);
                firefoxShim.shimSenderGetStats(window);
                firefoxShim.shimReceiverGetStats(window);
                firefoxShim.shimRTCDataChannel(window);
                firefoxShim.shimAddTransceiver(window);
                firefoxShim.shimCreateOffer(window);
                firefoxShim.shimCreateAnswer(window);

                commonShim.shimRTCIceCandidate(window);
                commonShim.shimConnectionState(window);
                commonShim.shimMaxMessageSize(window);
                commonShim.shimSendThrowTypeError(window);
                break;
              case 'edge':
                if (
                  !edgeShim ||
                  !edgeShim.shimPeerConnection ||
                  !options.shimEdge
                ) {
                  logging(
                    'MS edge shim is not included in this adapter release.'
                  );
                  return adapter;
                }
                logging('adapter.js shimming edge.');
                // Export to the adapter global object visible in the browser.
                adapter.browserShim = edgeShim;

                edgeShim.shimGetUserMedia(window);
                edgeShim.shimGetDisplayMedia(window);
                edgeShim.shimPeerConnection(window);
                edgeShim.shimReplaceTrack(window);

                // the edge shim implements the full RTCIceCandidate object.

                commonShim.shimMaxMessageSize(window);
                commonShim.shimSendThrowTypeError(window);
                break;
              case 'safari':
                if (!safariShim || !options.shimSafari) {
                  logging(
                    'Safari shim is not included in this adapter release.'
                  );
                  return adapter;
                }
                logging('adapter.js shimming safari.');
                // Export to the adapter global object visible in the browser.
                adapter.browserShim = safariShim;

                safariShim.shimRTCIceServerUrls(window);
                safariShim.shimCreateOfferLegacy(window);
                safariShim.shimCallbacksAPI(window);
                safariShim.shimLocalStreamsAPI(window);
                safariShim.shimRemoteStreamsAPI(window);
                safariShim.shimTrackEventTransceiver(window);
                safariShim.shimGetUserMedia(window);

                commonShim.shimRTCIceCandidate(window);
                commonShim.shimMaxMessageSize(window);
                commonShim.shimSendThrowTypeError(window);
                commonShim.removeAllowExtmapMixed(window);
                break;
              default:
                logging('Unsupported browser!');
                break;
            }

            return adapter;
          }

          // Browser shims.
        },
        {
          './chrome/chrome_shim': 3,
          './common_shim': 6,
          './edge/edge_shim': 7,
          './firefox/firefox_shim': 11,
          './safari/safari_shim': 14,
          './utils': 15,
        },
      ],
      3: [
        function(require, module, exports) {
          /*
           *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
           *
           *  Use of this source code is governed by a BSD-style license
           *  that can be found in the LICENSE file in the root of the source
           *  tree.
           */
          /* eslint-env node */
          'use strict';

          Object.defineProperty(exports, '__esModule', {
            value: true,
          });
          exports.shimGetDisplayMedia = exports.shimGetUserMedia = undefined;

          var _typeof =
            typeof Symbol === 'function' && typeof Symbol.iterator === 'symbol'
              ? function(obj) {
                  return typeof obj;
                }
              : function(obj) {
                  return obj &&
                    typeof Symbol === 'function' &&
                    obj.constructor === Symbol &&
                    obj !== Symbol.prototype
                    ? 'symbol'
                    : typeof obj;
                };

          var _getusermedia = require('./getusermedia');

          Object.defineProperty(exports, 'shimGetUserMedia', {
            enumerable: true,
            get: function get() {
              return _getusermedia.shimGetUserMedia;
            },
          });

          var _getdisplaymedia = require('./getdisplaymedia');

          Object.defineProperty(exports, 'shimGetDisplayMedia', {
            enumerable: true,
            get: function get() {
              return _getdisplaymedia.shimGetDisplayMedia;
            },
          });
          exports.shimMediaStream = shimMediaStream;
          exports.shimOnTrack = shimOnTrack;
          exports.shimGetSendersWithDtmf = shimGetSendersWithDtmf;
          exports.shimGetStats = shimGetStats;
          exports.shimSenderReceiverGetStats = shimSenderReceiverGetStats;
          exports.shimAddTrackRemoveTrackWithNative = shimAddTrackRemoveTrackWithNative;
          exports.shimAddTrackRemoveTrack = shimAddTrackRemoveTrack;
          exports.shimPeerConnection = shimPeerConnection;
          exports.fixNegotiationNeeded = fixNegotiationNeeded;

          var _utils = require('../utils.js');

          var utils = _interopRequireWildcard(_utils);

          function _interopRequireWildcard(obj) {
            if (obj && obj.__esModule) {
              return obj;
            } else {
              var newObj = {};
              if (obj != null) {
                for (var key in obj) {
                  if (Object.prototype.hasOwnProperty.call(obj, key))
                    newObj[key] = obj[key];
                }
              }
              newObj.default = obj;
              return newObj;
            }
          }

          function _defineProperty(obj, key, value) {
            if (key in obj) {
              Object.defineProperty(obj, key, {
                value: value,
                enumerable: true,
                configurable: true,
                writable: true,
              });
            } else {
              obj[key] = value;
            }
            return obj;
          }

          function shimMediaStream(window) {
            window.MediaStream = window.MediaStream || window.webkitMediaStream;
          }

          function shimOnTrack(window) {
            if (
              (typeof window === 'undefined'
                ? 'undefined'
                : _typeof(window)) === 'object' &&
              window.RTCPeerConnection &&
              !('ontrack' in window.RTCPeerConnection.prototype)
            ) {
              Object.defineProperty(
                window.RTCPeerConnection.prototype,
                'ontrack',
                {
                  get: function get() {
                    return this._ontrack;
                  },
                  set: function set(f) {
                    if (this._ontrack) {
                      this.removeEventListener('track', this._ontrack);
                    }
                    this.addEventListener('track', (this._ontrack = f));
                  },

                  enumerable: true,
                  configurable: true,
                }
              );
              var origSetRemoteDescription =
                window.RTCPeerConnection.prototype.setRemoteDescription;
              window.RTCPeerConnection.prototype.setRemoteDescription = function setRemoteDescription() {
                var _this = this;

                if (!this._ontrackpoly) {
                  this._ontrackpoly = function(e) {
                    // onaddstream does not fire when a track is added to an existing
                    // stream. But stream.onaddtrack is implemented so we use that.
                    e.stream.addEventListener('addtrack', function(te) {
                      var receiver = void 0;
                      if (window.RTCPeerConnection.prototype.getReceivers) {
                        receiver = _this.getReceivers().find(function(r) {
                          return r.track && r.track.id === te.track.id;
                        });
                      } else {
                        receiver = { track: te.track };
                      }

                      var event = new Event('track');
                      event.track = te.track;
                      event.receiver = receiver;
                      event.transceiver = { receiver: receiver };
                      event.streams = [e.stream];
                      _this.dispatchEvent(event);
                    });
                    e.stream.getTracks().forEach(function(track) {
                      var receiver = void 0;
                      if (window.RTCPeerConnection.prototype.getReceivers) {
                        receiver = _this.getReceivers().find(function(r) {
                          return r.track && r.track.id === track.id;
                        });
                      } else {
                        receiver = { track: track };
                      }
                      var event = new Event('track');
                      event.track = track;
                      event.receiver = receiver;
                      event.transceiver = { receiver: receiver };
                      event.streams = [e.stream];
                      _this.dispatchEvent(event);
                    });
                  };
                  this.addEventListener('addstream', this._ontrackpoly);
                }
                return origSetRemoteDescription.apply(this, arguments);
              };
            } else {
              // even if RTCRtpTransceiver is in window, it is only used and
              // emitted in unified-plan. Unfortunately this means we need
              // to unconditionally wrap the event.
              utils.wrapPeerConnectionEvent(window, 'track', function(e) {
                if (!e.transceiver) {
                  Object.defineProperty(e, 'transceiver', {
                    value: { receiver: e.receiver },
                  });
                }
                return e;
              });
            }
          }

          function shimGetSendersWithDtmf(window) {
            // Overrides addTrack/removeTrack, depends on shimAddTrackRemoveTrack.
            if (
              (typeof window === 'undefined'
                ? 'undefined'
                : _typeof(window)) === 'object' &&
              window.RTCPeerConnection &&
              !('getSenders' in window.RTCPeerConnection.prototype) &&
              'createDTMFSender' in window.RTCPeerConnection.prototype
            ) {
              var shimSenderWithDtmf = function shimSenderWithDtmf(pc, track) {
                return {
                  track: track,
                  get dtmf() {
                    if (this._dtmf === undefined) {
                      if (track.kind === 'audio') {
                        this._dtmf = pc.createDTMFSender(track);
                      } else {
                        this._dtmf = null;
                      }
                    }
                    return this._dtmf;
                  },
                  _pc: pc,
                };
              };

              // augment addTrack when getSenders is not available.
              if (!window.RTCPeerConnection.prototype.getSenders) {
                window.RTCPeerConnection.prototype.getSenders = function getSenders() {
                  this._senders = this._senders || [];
                  return this._senders.slice(); // return a copy of the internal state.
                };
                var origAddTrack = window.RTCPeerConnection.prototype.addTrack;
                window.RTCPeerConnection.prototype.addTrack = function addTrack(
                  track,
                  stream
                ) {
                  var sender = origAddTrack.apply(this, arguments);
                  if (!sender) {
                    sender = shimSenderWithDtmf(this, track);
                    this._senders.push(sender);
                  }
                  return sender;
                };

                var origRemoveTrack =
                  window.RTCPeerConnection.prototype.removeTrack;
                window.RTCPeerConnection.prototype.removeTrack = function removeTrack(
                  sender
                ) {
                  origRemoveTrack.apply(this, arguments);
                  var idx = this._senders.indexOf(sender);
                  if (idx !== -1) {
                    this._senders.splice(idx, 1);
                  }
                };
              }
              var origAddStream = window.RTCPeerConnection.prototype.addStream;
              window.RTCPeerConnection.prototype.addStream = function addStream(
                stream
              ) {
                var _this2 = this;

                this._senders = this._senders || [];
                origAddStream.apply(this, [stream]);
                stream.getTracks().forEach(function(track) {
                  _this2._senders.push(shimSenderWithDtmf(_this2, track));
                });
              };

              var origRemoveStream =
                window.RTCPeerConnection.prototype.removeStream;
              window.RTCPeerConnection.prototype.removeStream = function removeStream(
                stream
              ) {
                var _this3 = this;

                this._senders = this._senders || [];
                origRemoveStream.apply(this, [stream]);

                stream.getTracks().forEach(function(track) {
                  var sender = _this3._senders.find(function(s) {
                    return s.track === track;
                  });
                  if (sender) {
                    // remove sender
                    _this3._senders.splice(_this3._senders.indexOf(sender), 1);
                  }
                });
              };
            } else if (
              (typeof window === 'undefined'
                ? 'undefined'
                : _typeof(window)) === 'object' &&
              window.RTCPeerConnection &&
              'getSenders' in window.RTCPeerConnection.prototype &&
              'createDTMFSender' in window.RTCPeerConnection.prototype &&
              window.RTCRtpSender &&
              !('dtmf' in window.RTCRtpSender.prototype)
            ) {
              var origGetSenders =
                window.RTCPeerConnection.prototype.getSenders;
              window.RTCPeerConnection.prototype.getSenders = function getSenders() {
                var _this4 = this;

                var senders = origGetSenders.apply(this, []);
                senders.forEach(function(sender) {
                  return (sender._pc = _this4);
                });
                return senders;
              };

              Object.defineProperty(window.RTCRtpSender.prototype, 'dtmf', {
                get: function get() {
                  if (this._dtmf === undefined) {
                    if (this.track.kind === 'audio') {
                      this._dtmf = this._pc.createDTMFSender(this.track);
                    } else {
                      this._dtmf = null;
                    }
                  }
                  return this._dtmf;
                },
              });
            }
          }

          function shimGetStats(window) {
            if (!window.RTCPeerConnection) {
              return;
            }

            var origGetStats = window.RTCPeerConnection.prototype.getStats;
            window.RTCPeerConnection.prototype.getStats = function getStats() {
              var _this5 = this;

              var _arguments = Array.prototype.slice.call(arguments),
                selector = _arguments[0],
                onSucc = _arguments[1],
                onErr = _arguments[2];

              // If selector is a function then we are in the old style stats so just
              // pass back the original getStats format to avoid breaking old users.

              if (arguments.length > 0 && typeof selector === 'function') {
                return origGetStats.apply(this, arguments);
              }

              // When spec-style getStats is supported, return those when called with
              // either no arguments or the selector argument is null.
              if (
                origGetStats.length === 0 &&
                (arguments.length === 0 || typeof selector !== 'function')
              ) {
                return origGetStats.apply(this, []);
              }

              var fixChromeStats_ = function fixChromeStats_(response) {
                var standardReport = {};
                var reports = response.result();
                reports.forEach(function(report) {
                  var standardStats = {
                    id: report.id,
                    timestamp: report.timestamp,
                    type:
                      {
                        localcandidate: 'local-candidate',
                        remotecandidate: 'remote-candidate',
                      }[report.type] || report.type,
                  };
                  report.names().forEach(function(name) {
                    standardStats[name] = report.stat(name);
                  });
                  standardReport[standardStats.id] = standardStats;
                });

                return standardReport;
              };

              // shim getStats with maplike support
              var makeMapStats = function makeMapStats(stats) {
                return new Map(
                  Object.keys(stats).map(function(key) {
                    return [key, stats[key]];
                  })
                );
              };

              if (arguments.length >= 2) {
                var successCallbackWrapper_ = function successCallbackWrapper_(
                  response
                ) {
                  onSucc(makeMapStats(fixChromeStats_(response)));
                };

                return origGetStats.apply(this, [
                  successCallbackWrapper_,
                  selector,
                ]);
              }

              // promise-support
              return new Promise(function(resolve, reject) {
                origGetStats.apply(_this5, [
                  function(response) {
                    resolve(makeMapStats(fixChromeStats_(response)));
                  },
                  reject,
                ]);
              }).then(onSucc, onErr);
            };
          }

          function shimSenderReceiverGetStats(window) {
            if (
              !(
                (typeof window === 'undefined'
                  ? 'undefined'
                  : _typeof(window)) === 'object' &&
                window.RTCPeerConnection &&
                window.RTCRtpSender &&
                window.RTCRtpReceiver
              )
            ) {
              return;
            }

            // shim sender stats.
            if (!('getStats' in window.RTCRtpSender.prototype)) {
              var origGetSenders =
                window.RTCPeerConnection.prototype.getSenders;
              if (origGetSenders) {
                window.RTCPeerConnection.prototype.getSenders = function getSenders() {
                  var _this6 = this;

                  var senders = origGetSenders.apply(this, []);
                  senders.forEach(function(sender) {
                    return (sender._pc = _this6);
                  });
                  return senders;
                };
              }

              var origAddTrack = window.RTCPeerConnection.prototype.addTrack;
              if (origAddTrack) {
                window.RTCPeerConnection.prototype.addTrack = function addTrack() {
                  var sender = origAddTrack.apply(this, arguments);
                  sender._pc = this;
                  return sender;
                };
              }
              window.RTCRtpSender.prototype.getStats = function getStats() {
                var sender = this;
                return this._pc.getStats().then(function(result) {
                  return (
                    /* Note: this will include stats of all senders that
                     *   send a track with the same id as sender.track as
                     *   it is not possible to identify the RTCRtpSender.
                     */
                    utils.filterStats(result, sender.track, true)
                  );
                });
              };
            }

            // shim receiver stats.
            if (!('getStats' in window.RTCRtpReceiver.prototype)) {
              var origGetReceivers =
                window.RTCPeerConnection.prototype.getReceivers;
              if (origGetReceivers) {
                window.RTCPeerConnection.prototype.getReceivers = function getReceivers() {
                  var _this7 = this;

                  var receivers = origGetReceivers.apply(this, []);
                  receivers.forEach(function(receiver) {
                    return (receiver._pc = _this7);
                  });
                  return receivers;
                };
              }
              utils.wrapPeerConnectionEvent(window, 'track', function(e) {
                e.receiver._pc = e.srcElement;
                return e;
              });
              window.RTCRtpReceiver.prototype.getStats = function getStats() {
                var receiver = this;
                return this._pc.getStats().then(function(result) {
                  return utils.filterStats(result, receiver.track, false);
                });
              };
            }

            if (
              !(
                'getStats' in window.RTCRtpSender.prototype &&
                'getStats' in window.RTCRtpReceiver.prototype
              )
            ) {
              return;
            }

            // shim RTCPeerConnection.getStats(track).
            var origGetStats = window.RTCPeerConnection.prototype.getStats;
            window.RTCPeerConnection.prototype.getStats = function getStats() {
              if (
                arguments.length > 0 &&
                arguments[0] instanceof window.MediaStreamTrack
              ) {
                var track = arguments[0];
                var sender = void 0;
                var receiver = void 0;
                var err = void 0;
                this.getSenders().forEach(function(s) {
                  if (s.track === track) {
                    if (sender) {
                      err = true;
                    } else {
                      sender = s;
                    }
                  }
                });
                this.getReceivers().forEach(function(r) {
                  if (r.track === track) {
                    if (receiver) {
                      err = true;
                    } else {
                      receiver = r;
                    }
                  }
                  return r.track === track;
                });
                if (err || (sender && receiver)) {
                  return Promise.reject(
                    new DOMException(
                      'There are more than one sender or receiver for the track.',
                      'InvalidAccessError'
                    )
                  );
                } else if (sender) {
                  return sender.getStats();
                } else if (receiver) {
                  return receiver.getStats();
                }
                return Promise.reject(
                  new DOMException(
                    'There is no sender or receiver for the track.',
                    'InvalidAccessError'
                  )
                );
              }
              return origGetStats.apply(this, arguments);
            };
          }

          function shimAddTrackRemoveTrackWithNative(window) {
            // shim addTrack/removeTrack with native variants in order to make
            // the interactions with legacy getLocalStreams behave as in other browsers.
            // Keeps a mapping stream.id => [stream, rtpsenders...]
            window.RTCPeerConnection.prototype.getLocalStreams = function getLocalStreams() {
              var _this8 = this;

              this._shimmedLocalStreams = this._shimmedLocalStreams || {};
              return Object.keys(this._shimmedLocalStreams).map(function(
                streamId
              ) {
                return _this8._shimmedLocalStreams[streamId][0];
              });
            };

            var origAddTrack = window.RTCPeerConnection.prototype.addTrack;
            window.RTCPeerConnection.prototype.addTrack = function addTrack(
              track,
              stream
            ) {
              if (!stream) {
                return origAddTrack.apply(this, arguments);
              }
              this._shimmedLocalStreams = this._shimmedLocalStreams || {};

              var sender = origAddTrack.apply(this, arguments);
              if (!this._shimmedLocalStreams[stream.id]) {
                this._shimmedLocalStreams[stream.id] = [stream, sender];
              } else if (
                this._shimmedLocalStreams[stream.id].indexOf(sender) === -1
              ) {
                this._shimmedLocalStreams[stream.id].push(sender);
              }
              return sender;
            };

            var origAddStream = window.RTCPeerConnection.prototype.addStream;
            window.RTCPeerConnection.prototype.addStream = function addStream(
              stream
            ) {
              var _this9 = this;

              this._shimmedLocalStreams = this._shimmedLocalStreams || {};

              stream.getTracks().forEach(function(track) {
                var alreadyExists = _this9.getSenders().find(function(s) {
                  return s.track === track;
                });
                if (alreadyExists) {
                  throw new DOMException(
                    'Track already exists.',
                    'InvalidAccessError'
                  );
                }
              });
              var existingSenders = this.getSenders();
              origAddStream.apply(this, arguments);
              var newSenders = this.getSenders().filter(function(newSender) {
                return existingSenders.indexOf(newSender) === -1;
              });
              this._shimmedLocalStreams[stream.id] = [stream].concat(
                newSenders
              );
            };

            var origRemoveStream =
              window.RTCPeerConnection.prototype.removeStream;
            window.RTCPeerConnection.prototype.removeStream = function removeStream(
              stream
            ) {
              this._shimmedLocalStreams = this._shimmedLocalStreams || {};
              delete this._shimmedLocalStreams[stream.id];
              return origRemoveStream.apply(this, arguments);
            };

            var origRemoveTrack =
              window.RTCPeerConnection.prototype.removeTrack;
            window.RTCPeerConnection.prototype.removeTrack = function removeTrack(
              sender
            ) {
              var _this10 = this;

              this._shimmedLocalStreams = this._shimmedLocalStreams || {};
              if (sender) {
                Object.keys(this._shimmedLocalStreams).forEach(function(
                  streamId
                ) {
                  var idx = _this10._shimmedLocalStreams[streamId].indexOf(
                    sender
                  );
                  if (idx !== -1) {
                    _this10._shimmedLocalStreams[streamId].splice(idx, 1);
                  }
                  if (_this10._shimmedLocalStreams[streamId].length === 1) {
                    delete _this10._shimmedLocalStreams[streamId];
                  }
                });
              }
              return origRemoveTrack.apply(this, arguments);
            };
          }

          function shimAddTrackRemoveTrack(window) {
            if (!window.RTCPeerConnection) {
              return;
            }
            var browserDetails = utils.detectBrowser(window);
            // shim addTrack and removeTrack.
            if (
              window.RTCPeerConnection.prototype.addTrack &&
              browserDetails.version >= 65
            ) {
              return shimAddTrackRemoveTrackWithNative(window);
            }

            // also shim pc.getLocalStreams when addTrack is shimmed
            // to return the original streams.
            var origGetLocalStreams =
              window.RTCPeerConnection.prototype.getLocalStreams;
            window.RTCPeerConnection.prototype.getLocalStreams = function getLocalStreams() {
              var _this11 = this;

              var nativeStreams = origGetLocalStreams.apply(this);
              this._reverseStreams = this._reverseStreams || {};
              return nativeStreams.map(function(stream) {
                return _this11._reverseStreams[stream.id];
              });
            };

            var origAddStream = window.RTCPeerConnection.prototype.addStream;
            window.RTCPeerConnection.prototype.addStream = function addStream(
              stream
            ) {
              var _this12 = this;

              this._streams = this._streams || {};
              this._reverseStreams = this._reverseStreams || {};

              stream.getTracks().forEach(function(track) {
                var alreadyExists = _this12.getSenders().find(function(s) {
                  return s.track === track;
                });
                if (alreadyExists) {
                  throw new DOMException(
                    'Track already exists.',
                    'InvalidAccessError'
                  );
                }
              });
              // Add identity mapping for consistency with addTrack.
              // Unless this is being used with a stream from addTrack.
              if (!this._reverseStreams[stream.id]) {
                var newStream = new window.MediaStream(stream.getTracks());
                this._streams[stream.id] = newStream;
                this._reverseStreams[newStream.id] = stream;
                stream = newStream;
              }
              origAddStream.apply(this, [stream]);
            };

            var origRemoveStream =
              window.RTCPeerConnection.prototype.removeStream;
            window.RTCPeerConnection.prototype.removeStream = function removeStream(
              stream
            ) {
              this._streams = this._streams || {};
              this._reverseStreams = this._reverseStreams || {};

              origRemoveStream.apply(this, [
                this._streams[stream.id] || stream,
              ]);
              delete this._reverseStreams[
                this._streams[stream.id]
                  ? this._streams[stream.id].id
                  : stream.id
              ];
              delete this._streams[stream.id];
            };

            window.RTCPeerConnection.prototype.addTrack = function addTrack(
              track,
              stream
            ) {
              var _this13 = this;

              if (this.signalingState === 'closed') {
                throw new DOMException(
                  "The RTCPeerConnection's signalingState is 'closed'.",
                  'InvalidStateError'
                );
              }
              var streams = [].slice.call(arguments, 1);
              if (
                streams.length !== 1 ||
                !streams[0].getTracks().find(function(t) {
                  return t === track;
                })
              ) {
                // this is not fully correct but all we can manage without
                // [[associated MediaStreams]] internal slot.
                throw new DOMException(
                  'The adapter.js addTrack polyfill only supports a single ' +
                    ' stream which is associated with the specified track.',
                  'NotSupportedError'
                );
              }

              var alreadyExists = this.getSenders().find(function(s) {
                return s.track === track;
              });
              if (alreadyExists) {
                throw new DOMException(
                  'Track already exists.',
                  'InvalidAccessError'
                );
              }

              this._streams = this._streams || {};
              this._reverseStreams = this._reverseStreams || {};
              var oldStream = this._streams[stream.id];
              if (oldStream) {
                // this is using odd Chrome behaviour, use with caution:
                // https://bugs.chromium.org/p/webrtc/issues/detail?id=7815
                // Note: we rely on the high-level addTrack/dtmf shim to
                // create the sender with a dtmf sender.
                oldStream.addTrack(track);

                // Trigger ONN async.
                Promise.resolve().then(function() {
                  _this13.dispatchEvent(new Event('negotiationneeded'));
                });
              } else {
                var newStream = new window.MediaStream([track]);
                this._streams[stream.id] = newStream;
                this._reverseStreams[newStream.id] = stream;
                this.addStream(newStream);
              }
              return this.getSenders().find(function(s) {
                return s.track === track;
              });
            };

            // replace the internal stream id with the external one and
            // vice versa.
            function replaceInternalStreamId(pc, description) {
              var sdp = description.sdp;
              Object.keys(pc._reverseStreams || []).forEach(function(
                internalId
              ) {
                var externalStream = pc._reverseStreams[internalId];
                var internalStream = pc._streams[externalStream.id];
                sdp = sdp.replace(
                  new RegExp(internalStream.id, 'g'),
                  externalStream.id
                );
              });
              return new RTCSessionDescription({
                type: description.type,
                sdp: sdp,
              });
            }
            function replaceExternalStreamId(pc, description) {
              var sdp = description.sdp;
              Object.keys(pc._reverseStreams || []).forEach(function(
                internalId
              ) {
                var externalStream = pc._reverseStreams[internalId];
                var internalStream = pc._streams[externalStream.id];
                sdp = sdp.replace(
                  new RegExp(externalStream.id, 'g'),
                  internalStream.id
                );
              });
              return new RTCSessionDescription({
                type: description.type,
                sdp: sdp,
              });
            }
            ['createOffer', 'createAnswer'].forEach(function(method) {
              var nativeMethod = window.RTCPeerConnection.prototype[method];
              var methodObj = _defineProperty({}, method, function() {
                var _this14 = this;

                var args = arguments;
                var isLegacyCall =
                  arguments.length && typeof arguments[0] === 'function';
                if (isLegacyCall) {
                  return nativeMethod.apply(this, [
                    function(description) {
                      var desc = replaceInternalStreamId(_this14, description);
                      args[0].apply(null, [desc]);
                    },
                    function(err) {
                      if (args[1]) {
                        args[1].apply(null, err);
                      }
                    },
                    arguments[2],
                  ]);
                }
                return nativeMethod
                  .apply(this, arguments)
                  .then(function(description) {
                    return replaceInternalStreamId(_this14, description);
                  });
              });
              window.RTCPeerConnection.prototype[method] = methodObj[method];
            });

            var origSetLocalDescription =
              window.RTCPeerConnection.prototype.setLocalDescription;
            window.RTCPeerConnection.prototype.setLocalDescription = function setLocalDescription() {
              if (!arguments.length || !arguments[0].type) {
                return origSetLocalDescription.apply(this, arguments);
              }
              arguments[0] = replaceExternalStreamId(this, arguments[0]);
              return origSetLocalDescription.apply(this, arguments);
            };

            // TODO: mangle getStats: https://w3c.github.io/webrtc-stats/#dom-rtcmediastreamstats-streamidentifier

            var origLocalDescription = Object.getOwnPropertyDescriptor(
              window.RTCPeerConnection.prototype,
              'localDescription'
            );
            Object.defineProperty(
              window.RTCPeerConnection.prototype,
              'localDescription',
              {
                get: function get() {
                  var description = origLocalDescription.get.apply(this);
                  if (description.type === '') {
                    return description;
                  }
                  return replaceInternalStreamId(this, description);
                },
              }
            );

            window.RTCPeerConnection.prototype.removeTrack = function removeTrack(
              sender
            ) {
              var _this15 = this;

              if (this.signalingState === 'closed') {
                throw new DOMException(
                  "The RTCPeerConnection's signalingState is 'closed'.",
                  'InvalidStateError'
                );
              }
              // We can not yet check for sender instanceof RTCRtpSender
              // since we shim RTPSender. So we check if sender._pc is set.
              if (!sender._pc) {
                throw new DOMException(
                  'Argument 1 of RTCPeerConnection.removeTrack ' +
                    'does not implement interface RTCRtpSender.',
                  'TypeError'
                );
              }
              var isLocal = sender._pc === this;
              if (!isLocal) {
                throw new DOMException(
                  'Sender was not created by this connection.',
                  'InvalidAccessError'
                );
              }

              // Search for the native stream the senders track belongs to.
              this._streams = this._streams || {};
              var stream = void 0;
              Object.keys(this._streams).forEach(function(streamid) {
                var hasTrack = _this15._streams[streamid]
                  .getTracks()
                  .find(function(track) {
                    return sender.track === track;
                  });
                if (hasTrack) {
                  stream = _this15._streams[streamid];
                }
              });

              if (stream) {
                if (stream.getTracks().length === 1) {
                  // if this is the last track of the stream, remove the stream. This
                  // takes care of any shimmed _senders.
                  this.removeStream(this._reverseStreams[stream.id]);
                } else {
                  // relying on the same odd chrome behaviour as above.
                  stream.removeTrack(sender.track);
                }
                this.dispatchEvent(new Event('negotiationneeded'));
              }
            };
          }

          function shimPeerConnection(window) {
            var browserDetails = utils.detectBrowser(window);

            if (!window.RTCPeerConnection && window.webkitRTCPeerConnection) {
              // very basic support for old versions.
              window.RTCPeerConnection = window.webkitRTCPeerConnection;
            }
            if (!window.RTCPeerConnection) {
              return;
            }

            // shim implicit creation of RTCSessionDescription/RTCIceCandidate
            if (browserDetails.version < 53) {
              [
                'setLocalDescription',
                'setRemoteDescription',
                'addIceCandidate',
              ].forEach(function(method) {
                var nativeMethod = window.RTCPeerConnection.prototype[method];
                var methodObj = _defineProperty({}, method, function() {
                  arguments[0] = new (method === 'addIceCandidate'
                    ? window.RTCIceCandidate
                    : window.RTCSessionDescription)(arguments[0]);
                  return nativeMethod.apply(this, arguments);
                });
                window.RTCPeerConnection.prototype[method] = methodObj[method];
              });
            }

            // support for addIceCandidate(null or undefined)
            var nativeAddIceCandidate =
              window.RTCPeerConnection.prototype.addIceCandidate;
            window.RTCPeerConnection.prototype.addIceCandidate = function addIceCandidate() {
              if (!arguments[0]) {
                if (arguments[1]) {
                  arguments[1].apply(null);
                }
                return Promise.resolve();
              }
              // Firefox 68+ emits and processes {candidate: "", ...}, ignore
              // in older versions. Native support planned for Chrome M77.
              if (
                browserDetails.version < 78 &&
                arguments[0] &&
                arguments[0].candidate === ''
              ) {
                return Promise.resolve();
              }
              return nativeAddIceCandidate.apply(this, arguments);
            };
          }

          function fixNegotiationNeeded(window) {
            utils.wrapPeerConnectionEvent(window, 'negotiationneeded', function(
              e
            ) {
              var pc = e.target;
              if (pc.signalingState !== 'stable') {
                return;
              }
              return e;
            });
          }
        },
        { '../utils.js': 15, './getdisplaymedia': 4, './getusermedia': 5 },
      ],
      4: [
        function(require, module, exports) {
          /*
           *  Copyright (c) 2018 The adapter.js project authors. All Rights Reserved.
           *
           *  Use of this source code is governed by a BSD-style license
           *  that can be found in the LICENSE file in the root of the source
           *  tree.
           */
          /* eslint-env node */
          'use strict';

          Object.defineProperty(exports, '__esModule', {
            value: true,
          });
          exports.shimGetDisplayMedia = shimGetDisplayMedia;
          function shimGetDisplayMedia(window, getSourceId) {
            if (
              window.navigator.mediaDevices &&
              'getDisplayMedia' in window.navigator.mediaDevices
            ) {
              return;
            }
            if (!window.navigator.mediaDevices) {
              return;
            }
            // getSourceId is a function that returns a promise resolving with
            // the sourceId of the screen/window/tab to be shared.
            if (typeof getSourceId !== 'function') {
              console.error(
                'shimGetDisplayMedia: getSourceId argument is not ' +
                  'a function'
              );
              return;
            }
            window.navigator.mediaDevices.getDisplayMedia = function getDisplayMedia(
              constraints
            ) {
              return getSourceId(constraints).then(function(sourceId) {
                var widthSpecified =
                  constraints.video && constraints.video.width;
                var heightSpecified =
                  constraints.video && constraints.video.height;
                var frameRateSpecified =
                  constraints.video && constraints.video.frameRate;
                constraints.video = {
                  mandatory: {
                    chromeMediaSource: 'desktop',
                    chromeMediaSourceId: sourceId,
                    maxFrameRate: frameRateSpecified || 3,
                  },
                };
                if (widthSpecified) {
                  constraints.video.mandatory.maxWidth = widthSpecified;
                }
                if (heightSpecified) {
                  constraints.video.mandatory.maxHeight = heightSpecified;
                }
                return window.navigator.mediaDevices.getUserMedia(constraints);
              });
            };
          }
        },
        {},
      ],
      5: [
        function(require, module, exports) {
          /*
           *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
           *
           *  Use of this source code is governed by a BSD-style license
           *  that can be found in the LICENSE file in the root of the source
           *  tree.
           */
          /* eslint-env node */
          'use strict';

          Object.defineProperty(exports, '__esModule', {
            value: true,
          });

          var _typeof =
            typeof Symbol === 'function' && typeof Symbol.iterator === 'symbol'
              ? function(obj) {
                  return typeof obj;
                }
              : function(obj) {
                  return obj &&
                    typeof Symbol === 'function' &&
                    obj.constructor === Symbol &&
                    obj !== Symbol.prototype
                    ? 'symbol'
                    : typeof obj;
                };

          exports.shimGetUserMedia = shimGetUserMedia;

          var _utils = require('../utils.js');

          var utils = _interopRequireWildcard(_utils);

          function _interopRequireWildcard(obj) {
            if (obj && obj.__esModule) {
              return obj;
            } else {
              var newObj = {};
              if (obj != null) {
                for (var key in obj) {
                  if (Object.prototype.hasOwnProperty.call(obj, key))
                    newObj[key] = obj[key];
                }
              }
              newObj.default = obj;
              return newObj;
            }
          }

          var logging = utils.log;

          function shimGetUserMedia(window) {
            var navigator = window && window.navigator;

            if (!navigator.mediaDevices) {
              return;
            }

            var browserDetails = utils.detectBrowser(window);

            var constraintsToChrome_ = function constraintsToChrome_(c) {
              if (
                (typeof c === 'undefined' ? 'undefined' : _typeof(c)) !==
                  'object' ||
                c.mandatory ||
                c.optional
              ) {
                return c;
              }
              var cc = {};
              Object.keys(c).forEach(function(key) {
                if (
                  key === 'require' ||
                  key === 'advanced' ||
                  key === 'mediaSource'
                ) {
                  return;
                }
                var r =
                  _typeof(c[key]) === 'object' ? c[key] : { ideal: c[key] };
                if (r.exact !== undefined && typeof r.exact === 'number') {
                  r.min = r.max = r.exact;
                }
                var oldname_ = function oldname_(prefix, name) {
                  if (prefix) {
                    return (
                      prefix + name.charAt(0).toUpperCase() + name.slice(1)
                    );
                  }
                  return name === 'deviceId' ? 'sourceId' : name;
                };
                if (r.ideal !== undefined) {
                  cc.optional = cc.optional || [];
                  var oc = {};
                  if (typeof r.ideal === 'number') {
                    oc[oldname_('min', key)] = r.ideal;
                    cc.optional.push(oc);
                    oc = {};
                    oc[oldname_('max', key)] = r.ideal;
                    cc.optional.push(oc);
                  } else {
                    oc[oldname_('', key)] = r.ideal;
                    cc.optional.push(oc);
                  }
                }
                if (r.exact !== undefined && typeof r.exact !== 'number') {
                  cc.mandatory = cc.mandatory || {};
                  cc.mandatory[oldname_('', key)] = r.exact;
                } else {
                  ['min', 'max'].forEach(function(mix) {
                    if (r[mix] !== undefined) {
                      cc.mandatory = cc.mandatory || {};
                      cc.mandatory[oldname_(mix, key)] = r[mix];
                    }
                  });
                }
              });
              if (c.advanced) {
                cc.optional = (cc.optional || []).concat(c.advanced);
              }
              return cc;
            };

            var shimConstraints_ = function shimConstraints_(
              constraints,
              func
            ) {
              if (browserDetails.version >= 61) {
                return func(constraints);
              }
              constraints = JSON.parse(JSON.stringify(constraints));
              if (constraints && _typeof(constraints.audio) === 'object') {
                var remap = function remap(obj, a, b) {
                  if (a in obj && !(b in obj)) {
                    obj[b] = obj[a];
                    delete obj[a];
                  }
                };
                constraints = JSON.parse(JSON.stringify(constraints));
                remap(
                  constraints.audio,
                  'autoGainControl',
                  'googAutoGainControl'
                );
                remap(
                  constraints.audio,
                  'noiseSuppression',
                  'googNoiseSuppression'
                );
                constraints.audio = constraintsToChrome_(constraints.audio);
              }
              if (constraints && _typeof(constraints.video) === 'object') {
                // Shim facingMode for mobile & surface pro.
                var face = constraints.video.facingMode;
                face =
                  face &&
                  ((typeof face === 'undefined'
                    ? 'undefined'
                    : _typeof(face)) === 'object'
                    ? face
                    : { ideal: face });
                var getSupportedFacingModeLies = browserDetails.version < 66;

                if (
                  face &&
                  (face.exact === 'user' ||
                    face.exact === 'environment' ||
                    face.ideal === 'user' ||
                    face.ideal === 'environment') &&
                  !(
                    navigator.mediaDevices.getSupportedConstraints &&
                    navigator.mediaDevices.getSupportedConstraints()
                      .facingMode &&
                    !getSupportedFacingModeLies
                  )
                ) {
                  delete constraints.video.facingMode;
                  var matches = void 0;
                  if (
                    face.exact === 'environment' ||
                    face.ideal === 'environment'
                  ) {
                    matches = ['back', 'rear'];
                  } else if (face.exact === 'user' || face.ideal === 'user') {
                    matches = ['front'];
                  }
                  if (matches) {
                    // Look for matches in label, or use last cam for back (typical).
                    return navigator.mediaDevices
                      .enumerateDevices()
                      .then(function(devices) {
                        devices = devices.filter(function(d) {
                          return d.kind === 'videoinput';
                        });
                        var dev = devices.find(function(d) {
                          return matches.some(function(match) {
                            return d.label.toLowerCase().includes(match);
                          });
                        });
                        if (
                          !dev &&
                          devices.length &&
                          matches.includes('back')
                        ) {
                          dev = devices[devices.length - 1]; // more likely the back cam
                        }
                        if (dev) {
                          constraints.video.deviceId = face.exact
                            ? { exact: dev.deviceId }
                            : { ideal: dev.deviceId };
                        }
                        constraints.video = constraintsToChrome_(
                          constraints.video
                        );
                        logging('chrome: ' + JSON.stringify(constraints));
                        return func(constraints);
                      });
                  }
                }
                constraints.video = constraintsToChrome_(constraints.video);
              }
              logging('chrome: ' + JSON.stringify(constraints));
              return func(constraints);
            };

            var shimError_ = function shimError_(e) {
              if (browserDetails.version >= 64) {
                return e;
              }
              return {
                name:
                  {
                    PermissionDeniedError: 'NotAllowedError',
                    PermissionDismissedError: 'NotAllowedError',
                    InvalidStateError: 'NotAllowedError',
                    DevicesNotFoundError: 'NotFoundError',
                    ConstraintNotSatisfiedError: 'OverconstrainedError',
                    TrackStartError: 'NotReadableError',
                    MediaDeviceFailedDueToShutdown: 'NotAllowedError',
                    MediaDeviceKillSwitchOn: 'NotAllowedError',
                    TabCaptureError: 'AbortError',
                    ScreenCaptureError: 'AbortError',
                    DeviceCaptureError: 'AbortError',
                  }[e.name] || e.name,
                message: e.message,
                constraint: e.constraint || e.constraintName,
                toString: function toString() {
                  return this.name + (this.message && ': ') + this.message;
                },
              };
            };

            var getUserMedia_ = function getUserMedia_(
              constraints,
              onSuccess,
              onError
            ) {
              shimConstraints_(constraints, function(c) {
                navigator.webkitGetUserMedia(c, onSuccess, function(e) {
                  if (onError) {
                    onError(shimError_(e));
                  }
                });
              });
            };
            navigator.getUserMedia = getUserMedia_.bind(navigator);

            // Even though Chrome 45 has navigator.mediaDevices and a getUserMedia
            // function which returns a Promise, it does not accept spec-style
            // constraints.
            if (navigator.mediaDevices.getUserMedia) {
              var origGetUserMedia = navigator.mediaDevices.getUserMedia.bind(
                navigator.mediaDevices
              );
              navigator.mediaDevices.getUserMedia = function(cs) {
                return shimConstraints_(cs, function(c) {
                  return origGetUserMedia(c).then(
                    function(stream) {
                      if (
                        (c.audio && !stream.getAudioTracks().length) ||
                        (c.video && !stream.getVideoTracks().length)
                      ) {
                        stream.getTracks().forEach(function(track) {
                          track.stop();
                        });
                        throw new DOMException('', 'NotFoundError');
                      }
                      return stream;
                    },
                    function(e) {
                      return Promise.reject(shimError_(e));
                    }
                  );
                });
              };
            }
          }
        },
        { '../utils.js': 15 },
      ],
      6: [
        function(require, module, exports) {
          /*
           *  Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
           *
           *  Use of this source code is governed by a BSD-style license
           *  that can be found in the LICENSE file in the root of the source
           *  tree.
           */
          /* eslint-env node */
          'use strict';

          Object.defineProperty(exports, '__esModule', {
            value: true,
          });

          var _typeof =
            typeof Symbol === 'function' && typeof Symbol.iterator === 'symbol'
              ? function(obj) {
                  return typeof obj;
                }
              : function(obj) {
                  return obj &&
                    typeof Symbol === 'function' &&
                    obj.constructor === Symbol &&
                    obj !== Symbol.prototype
                    ? 'symbol'
                    : typeof obj;
                };

          exports.shimRTCIceCandidate = shimRTCIceCandidate;
          exports.shimMaxMessageSize = shimMaxMessageSize;
          exports.shimSendThrowTypeError = shimSendThrowTypeError;
          exports.shimConnectionState = shimConnectionState;
          exports.removeAllowExtmapMixed = removeAllowExtmapMixed;

          var _sdp = require('sdp');

          var _sdp2 = _interopRequireDefault(_sdp);

          var _utils = require('./utils');

          var utils = _interopRequireWildcard(_utils);

          function _interopRequireWildcard(obj) {
            if (obj && obj.__esModule) {
              return obj;
            } else {
              var newObj = {};
              if (obj != null) {
                for (var key in obj) {
                  if (Object.prototype.hasOwnProperty.call(obj, key))
                    newObj[key] = obj[key];
                }
              }
              newObj.default = obj;
              return newObj;
            }
          }

          function _interopRequireDefault(obj) {
            return obj && obj.__esModule ? obj : { default: obj };
          }

          function shimRTCIceCandidate(window) {
            // foundation is arbitrarily chosen as an indicator for full support for
            // https://w3c.github.io/webrtc-pc/#rtcicecandidate-interface
            if (
              !window.RTCIceCandidate ||
              (window.RTCIceCandidate &&
                'foundation' in window.RTCIceCandidate.prototype)
            ) {
              return;
            }

            var NativeRTCIceCandidate = window.RTCIceCandidate;
            window.RTCIceCandidate = function RTCIceCandidate(args) {
              // Remove the a= which shouldn't be part of the candidate string.
              if (
                (typeof args === 'undefined' ? 'undefined' : _typeof(args)) ===
                  'object' &&
                args.candidate &&
                args.candidate.indexOf('a=') === 0
              ) {
                args = JSON.parse(JSON.stringify(args));
                args.candidate = args.candidate.substr(2);
              }

              if (args.candidate && args.candidate.length) {
                // Augment the native candidate with the parsed fields.
                var nativeCandidate = new NativeRTCIceCandidate(args);
                var parsedCandidate = _sdp2.default.parseCandidate(
                  args.candidate
                );
                var augmentedCandidate = Object.assign(
                  nativeCandidate,
                  parsedCandidate
                );

                // Add a serializer that does not serialize the extra attributes.
                augmentedCandidate.toJSON = function toJSON() {
                  return {
                    candidate: augmentedCandidate.candidate,
                    sdpMid: augmentedCandidate.sdpMid,
                    sdpMLineIndex: augmentedCandidate.sdpMLineIndex,
                    usernameFragment: augmentedCandidate.usernameFragment,
                  };
                };
                return augmentedCandidate;
              }
              return new NativeRTCIceCandidate(args);
            };
            window.RTCIceCandidate.prototype = NativeRTCIceCandidate.prototype;

            // Hook up the augmented candidate in onicecandidate and
            // addEventListener('icecandidate', ...)
            utils.wrapPeerConnectionEvent(window, 'icecandidate', function(e) {
              if (e.candidate) {
                Object.defineProperty(e, 'candidate', {
                  value: new window.RTCIceCandidate(e.candidate),
                  writable: 'false',
                });
              }
              return e;
            });
          }

          function shimMaxMessageSize(window) {
            if (!window.RTCPeerConnection) {
              return;
            }
            var browserDetails = utils.detectBrowser(window);

            if (!('sctp' in window.RTCPeerConnection.prototype)) {
              Object.defineProperty(
                window.RTCPeerConnection.prototype,
                'sctp',
                {
                  get: function get() {
                    return typeof this._sctp === 'undefined'
                      ? null
                      : this._sctp;
                  },
                }
              );
            }

            var sctpInDescription = function sctpInDescription(description) {
              if (!description || !description.sdp) {
                return false;
              }
              var sections = _sdp2.default.splitSections(description.sdp);
              sections.shift();
              return sections.some(function(mediaSection) {
                var mLine = _sdp2.default.parseMLine(mediaSection);
                return (
                  mLine &&
                  mLine.kind === 'application' &&
                  mLine.protocol.indexOf('SCTP') !== -1
                );
              });
            };

            var getRemoteFirefoxVersion = function getRemoteFirefoxVersion(
              description
            ) {
              // TODO: Is there a better solution for detecting Firefox?
              var match = description.sdp.match(
                /mozilla...THIS_IS_SDPARTA-(\d+)/
              );
              if (match === null || match.length < 2) {
                return -1;
              }
              var version = parseInt(match[1], 10);
              // Test for NaN (yes, this is ugly)
              return version !== version ? -1 : version;
            };

            var getCanSendMaxMessageSize = function getCanSendMaxMessageSize(
              remoteIsFirefox
            ) {
              // Every implementation we know can send at least 64 KiB.
              // Note: Although Chrome is technically able to send up to 256 KiB, the
              //       data does not reach the other peer reliably.
              //       See: https://bugs.chromium.org/p/webrtc/issues/detail?id=8419
              var canSendMaxMessageSize = 65536;
              if (browserDetails.browser === 'firefox') {
                if (browserDetails.version < 57) {
                  if (remoteIsFirefox === -1) {
                    // FF < 57 will send in 16 KiB chunks using the deprecated PPID
                    // fragmentation.
                    canSendMaxMessageSize = 16384;
                  } else {
                    // However, other FF (and RAWRTC) can reassemble PPID-fragmented
                    // messages. Thus, supporting ~2 GiB when sending.
                    canSendMaxMessageSize = 2147483637;
                  }
                } else if (browserDetails.version < 60) {
                  // Currently, all FF >= 57 will reset the remote maximum message size
                  // to the default value when a data channel is created at a later
                  // stage. :(
                  // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1426831
                  canSendMaxMessageSize =
                    browserDetails.version === 57 ? 65535 : 65536;
                } else {
                  // FF >= 60 supports sending ~2 GiB
                  canSendMaxMessageSize = 2147483637;
                }
              }
              return canSendMaxMessageSize;
            };

            var getMaxMessageSize = function getMaxMessageSize(
              description,
              remoteIsFirefox
            ) {
              // Note: 65536 bytes is the default value from the SDP spec. Also,
              //       every implementation we know supports receiving 65536 bytes.
              var maxMessageSize = 65536;

              // FF 57 has a slightly incorrect default remote max message size, so
              // we need to adjust it here to avoid a failure when sending.
              // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1425697
              if (
                browserDetails.browser === 'firefox' &&
                browserDetails.version === 57
              ) {
                maxMessageSize = 65535;
              }

              var match = _sdp2.default.matchPrefix(
                description.sdp,
                'a=max-message-size:'
              );
              if (match.length > 0) {
                maxMessageSize = parseInt(match[0].substr(19), 10);
              } else if (
                browserDetails.browser === 'firefox' &&
                remoteIsFirefox !== -1
              ) {
                // If the maximum message size is not present in the remote SDP and
                // both local and remote are Firefox, the remote peer can receive
                // ~2 GiB.
                maxMessageSize = 2147483637;
              }
              return maxMessageSize;
            };

            var origSetRemoteDescription =
              window.RTCPeerConnection.prototype.setRemoteDescription;
            window.RTCPeerConnection.prototype.setRemoteDescription = function setRemoteDescription() {
              this._sctp = null;
              // Chrome decided to not expose .sctp in plan-b mode.
              // As usual, adapter.js has to do an 'ugly worakaround'
              // to cover up the mess.
              if (
                browserDetails.browser === 'chrome' &&
                browserDetails.version >= 76
              ) {
                var _getConfiguration = this.getConfiguration(),
                  sdpSemantics = _getConfiguration.sdpSemantics;

                if (sdpSemantics === 'plan-b') {
                  Object.defineProperty(this, 'sctp', {
                    get: function get() {
                      return typeof this._sctp === 'undefined'
                        ? null
                        : this._sctp;
                    },

                    enumerable: true,
                    configurable: true,
                  });
                }
              }

              if (sctpInDescription(arguments[0])) {
                // Check if the remote is FF.
                var isFirefox = getRemoteFirefoxVersion(arguments[0]);

                // Get the maximum message size the local peer is capable of sending
                var canSendMMS = getCanSendMaxMessageSize(isFirefox);

                // Get the maximum message size of the remote peer.
                var remoteMMS = getMaxMessageSize(arguments[0], isFirefox);

                // Determine final maximum message size
                var maxMessageSize = void 0;
                if (canSendMMS === 0 && remoteMMS === 0) {
                  maxMessageSize = Number.POSITIVE_INFINITY;
                } else if (canSendMMS === 0 || remoteMMS === 0) {
                  maxMessageSize = Math.max(canSendMMS, remoteMMS);
                } else {
                  maxMessageSize = Math.min(canSendMMS, remoteMMS);
                }

                // Create a dummy RTCSctpTransport object and the 'maxMessageSize'
                // attribute.
                var sctp = {};
                Object.defineProperty(sctp, 'maxMessageSize', {
                  get: function get() {
                    return maxMessageSize;
                  },
                });
                this._sctp = sctp;
              }

              return origSetRemoteDescription.apply(this, arguments);
            };
          }

          function shimSendThrowTypeError(window) {
            if (
              !(
                window.RTCPeerConnection &&
                'createDataChannel' in window.RTCPeerConnection.prototype
              )
            ) {
              return;
            }

            // Note: Although Firefox >= 57 has a native implementation, the maximum
            //       message size can be reset for all data channels at a later stage.
            //       See: https://bugzilla.mozilla.org/show_bug.cgi?id=1426831

            function wrapDcSend(dc, pc) {
              var origDataChannelSend = dc.send;
              dc.send = function send() {
                var data = arguments[0];
                var length = data.length || data.size || data.byteLength;
                if (
                  dc.readyState === 'open' &&
                  pc.sctp &&
                  length > pc.sctp.maxMessageSize
                ) {
                  throw new TypeError(
                    'Message too large (can send a maximum of ' +
                      pc.sctp.maxMessageSize +
                      ' bytes)'
                  );
                }
                return origDataChannelSend.apply(dc, arguments);
              };
            }
            var origCreateDataChannel =
              window.RTCPeerConnection.prototype.createDataChannel;
            window.RTCPeerConnection.prototype.createDataChannel = function createDataChannel() {
              var dataChannel = origCreateDataChannel.apply(this, arguments);
              wrapDcSend(dataChannel, this);
              return dataChannel;
            };
            utils.wrapPeerConnectionEvent(window, 'datachannel', function(e) {
              wrapDcSend(e.channel, e.target);
              return e;
            });
          }

          /* shims RTCConnectionState by pretending it is the same as iceConnectionState.
           * See https://bugs.chromium.org/p/webrtc/issues/detail?id=6145#c12
           * for why this is a valid hack in Chrome. In Firefox it is slightly incorrect
           * since DTLS failures would be hidden. See
           * https://bugzilla.mozilla.org/show_bug.cgi?id=1265827
           * for the Firefox tracking bug.
           */
          function shimConnectionState(window) {
            if (
              !window.RTCPeerConnection ||
              'connectionState' in window.RTCPeerConnection.prototype
            ) {
              return;
            }
            var proto = window.RTCPeerConnection.prototype;
            Object.defineProperty(proto, 'connectionState', {
              get: function get() {
                return (
                  {
                    completed: 'connected',
                    checking: 'connecting',
                  }[this.iceConnectionState] || this.iceConnectionState
                );
              },

              enumerable: true,
              configurable: true,
            });
            Object.defineProperty(proto, 'onconnectionstatechange', {
              get: function get() {
                return this._onconnectionstatechange || null;
              },
              set: function set(cb) {
                if (this._onconnectionstatechange) {
                  this.removeEventListener(
                    'connectionstatechange',
                    this._onconnectionstatechange
                  );
                  delete this._onconnectionstatechange;
                }
                if (cb) {
                  this.addEventListener(
                    'connectionstatechange',
                    (this._onconnectionstatechange = cb)
                  );
                }
              },

              enumerable: true,
              configurable: true,
            });

            ['setLocalDescription', 'setRemoteDescription'].forEach(function(
              method
            ) {
              var origMethod = proto[method];
              proto[method] = function() {
                if (!this._connectionstatechangepoly) {
                  this._connectionstatechangepoly = function(e) {
                    var pc = e.target;
                    if (pc._lastConnectionState !== pc.connectionState) {
                      pc._lastConnectionState = pc.connectionState;
                      var newEvent = new Event('connectionstatechange', e);
                      pc.dispatchEvent(newEvent);
                    }
                    return e;
                  };
                  this.addEventListener(
                    'iceconnectionstatechange',
                    this._connectionstatechangepoly
                  );
                }
                return origMethod.apply(this, arguments);
              };
            });
          }

          function removeAllowExtmapMixed(window) {
            /* remove a=extmap-allow-mixed for Chrome < M71 */
            if (!window.RTCPeerConnection) {
              return;
            }
            var browserDetails = utils.detectBrowser(window);
            if (
              browserDetails.browser === 'chrome' &&
              browserDetails.version >= 71
            ) {
              return;
            }
            var nativeSRD =
              window.RTCPeerConnection.prototype.setRemoteDescription;
            window.RTCPeerConnection.prototype.setRemoteDescription = function setRemoteDescription(
              desc
            ) {
              if (
                desc &&
                desc.sdp &&
                desc.sdp.indexOf('\na=extmap-allow-mixed') !== -1
              ) {
                desc.sdp = desc.sdp
                  .split('\n')
                  .filter(function(line) {
                    return line.trim() !== 'a=extmap-allow-mixed';
                  })
                  .join('\n');
              }
              return nativeSRD.apply(this, arguments);
            };
          }
        },
        { './utils': 15, sdp: 17 },
      ],
      7: [
        function(require, module, exports) {
          /*
           *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
           *
           *  Use of this source code is governed by a BSD-style license
           *  that can be found in the LICENSE file in the root of the source
           *  tree.
           */
          /* eslint-env node */
          'use strict';

          Object.defineProperty(exports, '__esModule', {
            value: true,
          });
          exports.shimGetDisplayMedia = exports.shimGetUserMedia = undefined;

          var _getusermedia = require('./getusermedia');

          Object.defineProperty(exports, 'shimGetUserMedia', {
            enumerable: true,
            get: function get() {
              return _getusermedia.shimGetUserMedia;
            },
          });

          var _getdisplaymedia = require('./getdisplaymedia');

          Object.defineProperty(exports, 'shimGetDisplayMedia', {
            enumerable: true,
            get: function get() {
              return _getdisplaymedia.shimGetDisplayMedia;
            },
          });
          exports.shimPeerConnection = shimPeerConnection;
          exports.shimReplaceTrack = shimReplaceTrack;

          var _utils = require('../utils');

          var utils = _interopRequireWildcard(_utils);

          var _filtericeservers = require('./filtericeservers');

          var _rtcpeerconnectionShim = require('rtcpeerconnection-shim');

          var _rtcpeerconnectionShim2 = _interopRequireDefault(
            _rtcpeerconnectionShim
          );

          function _interopRequireDefault(obj) {
            return obj && obj.__esModule ? obj : { default: obj };
          }

          function _interopRequireWildcard(obj) {
            if (obj && obj.__esModule) {
              return obj;
            } else {
              var newObj = {};
              if (obj != null) {
                for (var key in obj) {
                  if (Object.prototype.hasOwnProperty.call(obj, key))
                    newObj[key] = obj[key];
                }
              }
              newObj.default = obj;
              return newObj;
            }
          }

          function shimPeerConnection(window) {
            var browserDetails = utils.detectBrowser(window);

            if (window.RTCIceGatherer) {
              if (!window.RTCIceCandidate) {
                window.RTCIceCandidate = function RTCIceCandidate(args) {
                  return args;
                };
              }
              if (!window.RTCSessionDescription) {
                window.RTCSessionDescription = function RTCSessionDescription(
                  args
                ) {
                  return args;
                };
              }
              // this adds an additional event listener to MediaStrackTrack that signals
              // when a tracks enabled property was changed. Workaround for a bug in
              // addStream, see below. No longer required in 15025+
              if (browserDetails.version < 15025) {
                var origMSTEnabled = Object.getOwnPropertyDescriptor(
                  window.MediaStreamTrack.prototype,
                  'enabled'
                );
                Object.defineProperty(
                  window.MediaStreamTrack.prototype,
                  'enabled',
                  {
                    set: function set(value) {
                      origMSTEnabled.set.call(this, value);
                      var ev = new Event('enabled');
                      ev.enabled = value;
                      this.dispatchEvent(ev);
                    },
                  }
                );
              }
            }

            // ORTC defines the DTMF sender a bit different.
            // https://github.com/w3c/ortc/issues/714
            if (
              window.RTCRtpSender &&
              !('dtmf' in window.RTCRtpSender.prototype)
            ) {
              Object.defineProperty(window.RTCRtpSender.prototype, 'dtmf', {
                get: function get() {
                  if (this._dtmf === undefined) {
                    if (this.track.kind === 'audio') {
                      this._dtmf = new window.RTCDtmfSender(this);
                    } else if (this.track.kind === 'video') {
                      this._dtmf = null;
                    }
                  }
                  return this._dtmf;
                },
              });
            }
            // Edge currently only implements the RTCDtmfSender, not the
            // RTCDTMFSender alias. See http://draft.ortc.org/#rtcdtmfsender2*
            if (window.RTCDtmfSender && !window.RTCDTMFSender) {
              window.RTCDTMFSender = window.RTCDtmfSender;
            }

            var RTCPeerConnectionShim = (0, _rtcpeerconnectionShim2.default)(
              window,
              browserDetails.version
            );
            window.RTCPeerConnection = function RTCPeerConnection(config) {
              if (config && config.iceServers) {
                config.iceServers = (0, _filtericeservers.filterIceServers)(
                  config.iceServers,
                  browserDetails.version
                );
                utils.log('ICE servers after filtering:', config.iceServers);
              }
              return new RTCPeerConnectionShim(config);
            };
            window.RTCPeerConnection.prototype =
              RTCPeerConnectionShim.prototype;
          }

          function shimReplaceTrack(window) {
            // ORTC has replaceTrack -- https://github.com/w3c/ortc/issues/614
            if (
              window.RTCRtpSender &&
              !('replaceTrack' in window.RTCRtpSender.prototype)
            ) {
              window.RTCRtpSender.prototype.replaceTrack =
                window.RTCRtpSender.prototype.setTrack;
            }
          }
        },
        {
          '../utils': 15,
          './filtericeservers': 8,
          './getdisplaymedia': 9,
          './getusermedia': 10,
          'rtcpeerconnection-shim': 16,
        },
      ],
      8: [
        function(require, module, exports) {
          /*
           *  Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.
           *
           *  Use of this source code is governed by a BSD-style license
           *  that can be found in the LICENSE file in the root of the source
           *  tree.
           */
          /* eslint-env node */
          'use strict';

          Object.defineProperty(exports, '__esModule', {
            value: true,
          });
          exports.filterIceServers = filterIceServers;

          var _utils = require('../utils');

          var utils = _interopRequireWildcard(_utils);

          function _interopRequireWildcard(obj) {
            if (obj && obj.__esModule) {
              return obj;
            } else {
              var newObj = {};
              if (obj != null) {
                for (var key in obj) {
                  if (Object.prototype.hasOwnProperty.call(obj, key))
                    newObj[key] = obj[key];
                }
              }
              newObj.default = obj;
              return newObj;
            }
          }

          // Edge does not like
          // 1) stun: filtered after 14393 unless ?transport=udp is present
          // 2) turn: that does not have all of turn:host:port?transport=udp
          // 3) turn: with ipv6 addresses
          // 4) turn: occurring muliple times
          function filterIceServers(iceServers, edgeVersion) {
            var hasTurn = false;
            iceServers = JSON.parse(JSON.stringify(iceServers));
            return iceServers.filter(function(server) {
              if (server && (server.urls || server.url)) {
                var urls = server.urls || server.url;
                if (server.url && !server.urls) {
                  utils.deprecated('RTCIceServer.url', 'RTCIceServer.urls');
                }
                var isString = typeof urls === 'string';
                if (isString) {
                  urls = [urls];
                }
                urls = urls.filter(function(url) {
                  // filter STUN unconditionally.
                  if (url.indexOf('stun:') === 0) {
                    return false;
                  }

                  var validTurn =
                    url.startsWith('turn') &&
                    !url.startsWith('turn:[') &&
                    url.includes('transport=udp');
                  if (validTurn && !hasTurn) {
                    hasTurn = true;
                    return true;
                  }
                  return validTurn && !hasTurn;
                });

                delete server.url;
                server.urls = isString ? urls[0] : urls;
                return !!urls.length;
              }
            });
          }
        },
        { '../utils': 15 },
      ],
      9: [
        function(require, module, exports) {
          /*
           *  Copyright (c) 2018 The adapter.js project authors. All Rights Reserved.
           *
           *  Use of this source code is governed by a BSD-style license
           *  that can be found in the LICENSE file in the root of the source
           *  tree.
           */
          /* eslint-env node */
          'use strict';

          Object.defineProperty(exports, '__esModule', {
            value: true,
          });
          exports.shimGetDisplayMedia = shimGetDisplayMedia;
          function shimGetDisplayMedia(window) {
            if (!('getDisplayMedia' in window.navigator)) {
              return;
            }
            if (!window.navigator.mediaDevices) {
              return;
            }
            if (
              window.navigator.mediaDevices &&
              'getDisplayMedia' in window.navigator.mediaDevices
            ) {
              return;
            }
            window.navigator.mediaDevices.getDisplayMedia = window.navigator.getDisplayMedia.bind(
              window.navigator
            );
          }
        },
        {},
      ],
      10: [
        function(require, module, exports) {
          /*
           *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
           *
           *  Use of this source code is governed by a BSD-style license
           *  that can be found in the LICENSE file in the root of the source
           *  tree.
           */
          /* eslint-env node */
          'use strict';

          Object.defineProperty(exports, '__esModule', {
            value: true,
          });
          exports.shimGetUserMedia = shimGetUserMedia;
          function shimGetUserMedia(window) {
            var navigator = window && window.navigator;

            var shimError_ = function shimError_(e) {
              return {
                name:
                  { PermissionDeniedError: 'NotAllowedError' }[e.name] ||
                  e.name,
                message: e.message,
                constraint: e.constraint,
                toString: function toString() {
                  return this.name;
                },
              };
            };

            // getUserMedia error shim.
            var origGetUserMedia = navigator.mediaDevices.getUserMedia.bind(
              navigator.mediaDevices
            );
            navigator.mediaDevices.getUserMedia = function(c) {
              return origGetUserMedia(c).catch(function(e) {
                return Promise.reject(shimError_(e));
              });
            };
          }
        },
        {},
      ],
      11: [
        function(require, module, exports) {
          /*
           *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
           *
           *  Use of this source code is governed by a BSD-style license
           *  that can be found in the LICENSE file in the root of the source
           *  tree.
           */
          /* eslint-env node */
          'use strict';

          Object.defineProperty(exports, '__esModule', {
            value: true,
          });
          exports.shimGetDisplayMedia = exports.shimGetUserMedia = undefined;

          var _typeof =
            typeof Symbol === 'function' && typeof Symbol.iterator === 'symbol'
              ? function(obj) {
                  return typeof obj;
                }
              : function(obj) {
                  return obj &&
                    typeof Symbol === 'function' &&
                    obj.constructor === Symbol &&
                    obj !== Symbol.prototype
                    ? 'symbol'
                    : typeof obj;
                };

          var _getusermedia = require('./getusermedia');

          Object.defineProperty(exports, 'shimGetUserMedia', {
            enumerable: true,
            get: function get() {
              return _getusermedia.shimGetUserMedia;
            },
          });

          var _getdisplaymedia = require('./getdisplaymedia');

          Object.defineProperty(exports, 'shimGetDisplayMedia', {
            enumerable: true,
            get: function get() {
              return _getdisplaymedia.shimGetDisplayMedia;
            },
          });
          exports.shimOnTrack = shimOnTrack;
          exports.shimPeerConnection = shimPeerConnection;
          exports.shimSenderGetStats = shimSenderGetStats;
          exports.shimReceiverGetStats = shimReceiverGetStats;
          exports.shimRemoveStream = shimRemoveStream;
          exports.shimRTCDataChannel = shimRTCDataChannel;
          exports.shimAddTransceiver = shimAddTransceiver;
          exports.shimCreateOffer = shimCreateOffer;
          exports.shimCreateAnswer = shimCreateAnswer;

          var _utils = require('../utils');

          var utils = _interopRequireWildcard(_utils);

          function _interopRequireWildcard(obj) {
            if (obj && obj.__esModule) {
              return obj;
            } else {
              var newObj = {};
              if (obj != null) {
                for (var key in obj) {
                  if (Object.prototype.hasOwnProperty.call(obj, key))
                    newObj[key] = obj[key];
                }
              }
              newObj.default = obj;
              return newObj;
            }
          }

          function _defineProperty(obj, key, value) {
            if (key in obj) {
              Object.defineProperty(obj, key, {
                value: value,
                enumerable: true,
                configurable: true,
                writable: true,
              });
            } else {
              obj[key] = value;
            }
            return obj;
          }

          function shimOnTrack(window) {
            if (
              (typeof window === 'undefined'
                ? 'undefined'
                : _typeof(window)) === 'object' &&
              window.RTCTrackEvent &&
              'receiver' in window.RTCTrackEvent.prototype &&
              !('transceiver' in window.RTCTrackEvent.prototype)
            ) {
              Object.defineProperty(
                window.RTCTrackEvent.prototype,
                'transceiver',
                {
                  get: function get() {
                    return { receiver: this.receiver };
                  },
                }
              );
            }
          }

          function shimPeerConnection(window) {
            var browserDetails = utils.detectBrowser(window);

            if (
              (typeof window === 'undefined'
                ? 'undefined'
                : _typeof(window)) !== 'object' ||
              !(window.RTCPeerConnection || window.mozRTCPeerConnection)
            ) {
              return; // probably media.peerconnection.enabled=false in about:config
            }
            if (!window.RTCPeerConnection && window.mozRTCPeerConnection) {
              // very basic support for old versions.
              window.RTCPeerConnection = window.mozRTCPeerConnection;
            }

            if (browserDetails.version < 53) {
              // shim away need for obsolete RTCIceCandidate/RTCSessionDescription.
              [
                'setLocalDescription',
                'setRemoteDescription',
                'addIceCandidate',
              ].forEach(function(method) {
                var nativeMethod = window.RTCPeerConnection.prototype[method];
                var methodObj = _defineProperty({}, method, function() {
                  arguments[0] = new (method === 'addIceCandidate'
                    ? window.RTCIceCandidate
                    : window.RTCSessionDescription)(arguments[0]);
                  return nativeMethod.apply(this, arguments);
                });
                window.RTCPeerConnection.prototype[method] = methodObj[method];
              });
            }

            // support for addIceCandidate(null or undefined)
            // as well as ignoring {sdpMid, candidate: ""}
            if (browserDetails.version < 68) {
              var nativeAddIceCandidate =
                window.RTCPeerConnection.prototype.addIceCandidate;
              window.RTCPeerConnection.prototype.addIceCandidate = function addIceCandidate() {
                if (!arguments[0]) {
                  if (arguments[1]) {
                    arguments[1].apply(null);
                  }
                  return Promise.resolve();
                }
                // Firefox 68+ emits and processes {candidate: "", ...}, ignore
                // in older versions.
                if (arguments[0] && arguments[0].candidate === '') {
                  return Promise.resolve();
                }
                return nativeAddIceCandidate.apply(this, arguments);
              };
            }

            var modernStatsTypes = {
              inboundrtp: 'inbound-rtp',
              outboundrtp: 'outbound-rtp',
              candidatepair: 'candidate-pair',
              localcandidate: 'local-candidate',
              remotecandidate: 'remote-candidate',
            };

            var nativeGetStats = window.RTCPeerConnection.prototype.getStats;
            window.RTCPeerConnection.prototype.getStats = function getStats() {
              var _arguments = Array.prototype.slice.call(arguments),
                selector = _arguments[0],
                onSucc = _arguments[1],
                onErr = _arguments[2];

              return nativeGetStats
                .apply(this, [selector || null])
                .then(function(stats) {
                  if (browserDetails.version < 53 && !onSucc) {
                    // Shim only promise getStats with spec-hyphens in type names
                    // Leave callback version alone; misc old uses of forEach before Map
                    try {
                      stats.forEach(function(stat) {
                        stat.type = modernStatsTypes[stat.type] || stat.type;
                      });
                    } catch (e) {
                      if (e.name !== 'TypeError') {
                        throw e;
                      }
                      // Avoid TypeError: "type" is read-only, in old versions. 34-43ish
                      stats.forEach(function(stat, i) {
                        stats.set(
                          i,
                          Object.assign({}, stat, {
                            type: modernStatsTypes[stat.type] || stat.type,
                          })
                        );
                      });
                    }
                  }
                  return stats;
                })
                .then(onSucc, onErr);
            };
          }

          function shimSenderGetStats(window) {
            if (
              !(
                (typeof window === 'undefined'
                  ? 'undefined'
                  : _typeof(window)) === 'object' &&
                window.RTCPeerConnection &&
                window.RTCRtpSender
              )
            ) {
              return;
            }
            if (
              window.RTCRtpSender &&
              'getStats' in window.RTCRtpSender.prototype
            ) {
              return;
            }
            var origGetSenders = window.RTCPeerConnection.prototype.getSenders;
            if (origGetSenders) {
              window.RTCPeerConnection.prototype.getSenders = function getSenders() {
                var _this = this;

                var senders = origGetSenders.apply(this, []);
                senders.forEach(function(sender) {
                  return (sender._pc = _this);
                });
                return senders;
              };
            }

            var origAddTrack = window.RTCPeerConnection.prototype.addTrack;
            if (origAddTrack) {
              window.RTCPeerConnection.prototype.addTrack = function addTrack() {
                var sender = origAddTrack.apply(this, arguments);
                sender._pc = this;
                return sender;
              };
            }
            window.RTCRtpSender.prototype.getStats = function getStats() {
              return this.track
                ? this._pc.getStats(this.track)
                : Promise.resolve(new Map());
            };
          }

          function shimReceiverGetStats(window) {
            if (
              !(
                (typeof window === 'undefined'
                  ? 'undefined'
                  : _typeof(window)) === 'object' &&
                window.RTCPeerConnection &&
                window.RTCRtpSender
              )
            ) {
              return;
            }
            if (
              window.RTCRtpSender &&
              'getStats' in window.RTCRtpReceiver.prototype
            ) {
              return;
            }
            var origGetReceivers =
              window.RTCPeerConnection.prototype.getReceivers;
            if (origGetReceivers) {
              window.RTCPeerConnection.prototype.getReceivers = function getReceivers() {
                var _this2 = this;

                var receivers = origGetReceivers.apply(this, []);
                receivers.forEach(function(receiver) {
                  return (receiver._pc = _this2);
                });
                return receivers;
              };
            }
            utils.wrapPeerConnectionEvent(window, 'track', function(e) {
              e.receiver._pc = e.srcElement;
              return e;
            });
            window.RTCRtpReceiver.prototype.getStats = function getStats() {
              return this._pc.getStats(this.track);
            };
          }

          function shimRemoveStream(window) {
            if (
              !window.RTCPeerConnection ||
              'removeStream' in window.RTCPeerConnection.prototype
            ) {
              return;
            }
            window.RTCPeerConnection.prototype.removeStream = function removeStream(
              stream
            ) {
              var _this3 = this;

              utils.deprecated('removeStream', 'removeTrack');
              this.getSenders().forEach(function(sender) {
                if (sender.track && stream.getTracks().includes(sender.track)) {
                  _this3.removeTrack(sender);
                }
              });
            };
          }

          function shimRTCDataChannel(window) {
            // rename DataChannel to RTCDataChannel (native fix in FF60):
            // https://bugzilla.mozilla.org/show_bug.cgi?id=1173851
            if (window.DataChannel && !window.RTCDataChannel) {
              window.RTCDataChannel = window.DataChannel;
            }
          }

          function shimAddTransceiver(window) {
            // https://github.com/webrtcHacks/adapter/issues/998#issuecomment-516921647
            // Firefox ignores the init sendEncodings options passed to addTransceiver
            // https://bugzilla.mozilla.org/show_bug.cgi?id=1396918
            if (
              !(
                (typeof window === 'undefined'
                  ? 'undefined'
                  : _typeof(window)) === 'object' && window.RTCPeerConnection
              )
            ) {
              return;
            }
            var origAddTransceiver =
              window.RTCPeerConnection.prototype.addTransceiver;
            if (origAddTransceiver) {
              window.RTCPeerConnection.prototype.addTransceiver = function addTransceiver() {
                this.setParametersPromises = [];
                var initParameters = arguments[1];
                var shouldPerformCheck =
                  initParameters && 'sendEncodings' in initParameters;
                if (shouldPerformCheck) {
                  // If sendEncodings params are provided, validate grammar
                  initParameters.sendEncodings.forEach(function(encodingParam) {
                    if ('rid' in encodingParam) {
                      var ridRegex = /^[a-z0-9]{0,16}$/i;
                      if (!ridRegex.test(encodingParam.rid)) {
                        throw new TypeError('Invalid RID value provided.');
                      }
                    }
                    if ('scaleResolutionDownBy' in encodingParam) {
                      if (
                        !(
                          parseFloat(encodingParam.scaleResolutionDownBy) >= 1.0
                        )
                      ) {
                        throw new RangeError(
                          'scale_resolution_down_by must be >= 1.0'
                        );
                      }
                    }
                    if ('maxFramerate' in encodingParam) {
                      if (!(parseFloat(encodingParam.maxFramerate) >= 0)) {
                        throw new RangeError('max_framerate must be >= 0.0');
                      }
                    }
                  });
                }
                var transceiver = origAddTransceiver.apply(this, arguments);
                if (shouldPerformCheck) {
                  // Check if the init options were applied. If not we do this in an
                  // asynchronous way and save the promise reference in a global object.
                  // This is an ugly hack, but at the same time is way more robust than
                  // checking the sender parameters before and after the createOffer
                  // Also note that after the createoffer we are not 100% sure that
                  // the params were asynchronously applied so we might miss the
                  // opportunity to recreate offer.
                  var sender = transceiver.sender;

                  var params = sender.getParameters();
                  if (!('encodings' in params)) {
                    params.encodings = initParameters.sendEncodings;
                    this.setParametersPromises.push(
                      sender.setParameters(params).catch(function() {})
                    );
                  }
                }
                return transceiver;
              };
            }
          }

          function shimCreateOffer(window) {
            // https://github.com/webrtcHacks/adapter/issues/998#issuecomment-516921647
            // Firefox ignores the init sendEncodings options passed to addTransceiver
            // https://bugzilla.mozilla.org/show_bug.cgi?id=1396918
            if (
              !(
                (typeof window === 'undefined'
                  ? 'undefined'
                  : _typeof(window)) === 'object' && window.RTCPeerConnection
              )
            ) {
              return;
            }
            var origCreateOffer =
              window.RTCPeerConnection.prototype.createOffer;
            window.RTCPeerConnection.prototype.createOffer = function createOffer() {
              var _this4 = this,
                _arguments2 = arguments;

              if (
                this.setParametersPromises &&
                this.setParametersPromises.length
              ) {
                return Promise.all(this.setParametersPromises)
                  .then(function() {
                    return origCreateOffer.apply(_this4, _arguments2);
                  })
                  .finally(function() {
                    _this4.setParametersPromises = [];
                  });
              }
              return origCreateOffer.apply(this, arguments);
            };
          }

          function shimCreateAnswer(window) {
            // https://github.com/webrtcHacks/adapter/issues/998#issuecomment-516921647
            // Firefox ignores the init sendEncodings options passed to addTransceiver
            // https://bugzilla.mozilla.org/show_bug.cgi?id=1396918
            if (
              !(
                (typeof window === 'undefined'
                  ? 'undefined'
                  : _typeof(window)) === 'object' && window.RTCPeerConnection
              )
            ) {
              return;
            }
            var origCreateAnswer =
              window.RTCPeerConnection.prototype.createAnswer;
            window.RTCPeerConnection.prototype.createAnswer = function createAnswer() {
              var _this5 = this,
                _arguments3 = arguments;

              if (
                this.setParametersPromises &&
                this.setParametersPromises.length
              ) {
                return Promise.all(this.setParametersPromises)
                  .then(function() {
                    return origCreateAnswer.apply(_this5, _arguments3);
                  })
                  .finally(function() {
                    _this5.setParametersPromises = [];
                  });
              }
              return origCreateAnswer.apply(this, arguments);
            };
          }
        },
        { '../utils': 15, './getdisplaymedia': 12, './getusermedia': 13 },
      ],
      12: [
        function(require, module, exports) {
          /*
           *  Copyright (c) 2018 The adapter.js project authors. All Rights Reserved.
           *
           *  Use of this source code is governed by a BSD-style license
           *  that can be found in the LICENSE file in the root of the source
           *  tree.
           */
          /* eslint-env node */
          'use strict';

          Object.defineProperty(exports, '__esModule', {
            value: true,
          });
          exports.shimGetDisplayMedia = shimGetDisplayMedia;
          function shimGetDisplayMedia(window, preferredMediaSource) {
            if (
              window.navigator.mediaDevices &&
              'getDisplayMedia' in window.navigator.mediaDevices
            ) {
              return;
            }
            if (!window.navigator.mediaDevices) {
              return;
            }
            window.navigator.mediaDevices.getDisplayMedia = function getDisplayMedia(
              constraints
            ) {
              if (!(constraints && constraints.video)) {
                var err = new DOMException(
                  'getDisplayMedia without video ' + 'constraints is undefined'
                );
                err.name = 'NotFoundError';
                // from https://heycam.github.io/webidl/#idl-DOMException-error-names
                err.code = 8;
                return Promise.reject(err);
              }
              if (constraints.video === true) {
                constraints.video = { mediaSource: preferredMediaSource };
              } else {
                constraints.video.mediaSource = preferredMediaSource;
              }
              return window.navigator.mediaDevices.getUserMedia(constraints);
            };
          }
        },
        {},
      ],
      13: [
        function(require, module, exports) {
          /*
           *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
           *
           *  Use of this source code is governed by a BSD-style license
           *  that can be found in the LICENSE file in the root of the source
           *  tree.
           */
          /* eslint-env node */
          'use strict';

          Object.defineProperty(exports, '__esModule', {
            value: true,
          });

          var _typeof =
            typeof Symbol === 'function' && typeof Symbol.iterator === 'symbol'
              ? function(obj) {
                  return typeof obj;
                }
              : function(obj) {
                  return obj &&
                    typeof Symbol === 'function' &&
                    obj.constructor === Symbol &&
                    obj !== Symbol.prototype
                    ? 'symbol'
                    : typeof obj;
                };

          exports.shimGetUserMedia = shimGetUserMedia;

          var _utils = require('../utils');

          var utils = _interopRequireWildcard(_utils);

          function _interopRequireWildcard(obj) {
            if (obj && obj.__esModule) {
              return obj;
            } else {
              var newObj = {};
              if (obj != null) {
                for (var key in obj) {
                  if (Object.prototype.hasOwnProperty.call(obj, key))
                    newObj[key] = obj[key];
                }
              }
              newObj.default = obj;
              return newObj;
            }
          }

          function shimGetUserMedia(window) {
            var browserDetails = utils.detectBrowser(window);
            var navigator = window && window.navigator;
            var MediaStreamTrack = window && window.MediaStreamTrack;

            navigator.getUserMedia = function(constraints, onSuccess, onError) {
              // Replace Firefox 44+'s deprecation warning with unprefixed version.
              utils.deprecated(
                'navigator.getUserMedia',
                'navigator.mediaDevices.getUserMedia'
              );
              navigator.mediaDevices
                .getUserMedia(constraints)
                .then(onSuccess, onError);
            };

            if (
              !(
                browserDetails.version > 55 &&
                'autoGainControl' in
                  navigator.mediaDevices.getSupportedConstraints()
              )
            ) {
              var remap = function remap(obj, a, b) {
                if (a in obj && !(b in obj)) {
                  obj[b] = obj[a];
                  delete obj[a];
                }
              };

              var nativeGetUserMedia = navigator.mediaDevices.getUserMedia.bind(
                navigator.mediaDevices
              );
              navigator.mediaDevices.getUserMedia = function(c) {
                if (
                  (typeof c === 'undefined' ? 'undefined' : _typeof(c)) ===
                    'object' &&
                  _typeof(c.audio) === 'object'
                ) {
                  c = JSON.parse(JSON.stringify(c));
                  remap(c.audio, 'autoGainControl', 'mozAutoGainControl');
                  remap(c.audio, 'noiseSuppression', 'mozNoiseSuppression');
                }
                return nativeGetUserMedia(c);
              };

              if (MediaStreamTrack && MediaStreamTrack.prototype.getSettings) {
                var nativeGetSettings = MediaStreamTrack.prototype.getSettings;
                MediaStreamTrack.prototype.getSettings = function() {
                  var obj = nativeGetSettings.apply(this, arguments);
                  remap(obj, 'mozAutoGainControl', 'autoGainControl');
                  remap(obj, 'mozNoiseSuppression', 'noiseSuppression');
                  return obj;
                };
              }

              if (
                MediaStreamTrack &&
                MediaStreamTrack.prototype.applyConstraints
              ) {
                var nativeApplyConstraints =
                  MediaStreamTrack.prototype.applyConstraints;
                MediaStreamTrack.prototype.applyConstraints = function(c) {
                  if (
                    this.kind === 'audio' &&
                    (typeof c === 'undefined' ? 'undefined' : _typeof(c)) ===
                      'object'
                  ) {
                    c = JSON.parse(JSON.stringify(c));
                    remap(c, 'autoGainControl', 'mozAutoGainControl');
                    remap(c, 'noiseSuppression', 'mozNoiseSuppression');
                  }
                  return nativeApplyConstraints.apply(this, [c]);
                };
              }
            }
          }
        },
        { '../utils': 15 },
      ],
      14: [
        function(require, module, exports) {
          /*
           *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
           *
           *  Use of this source code is governed by a BSD-style license
           *  that can be found in the LICENSE file in the root of the source
           *  tree.
           */
          'use strict';

          Object.defineProperty(exports, '__esModule', {
            value: true,
          });

          var _typeof =
            typeof Symbol === 'function' && typeof Symbol.iterator === 'symbol'
              ? function(obj) {
                  return typeof obj;
                }
              : function(obj) {
                  return obj &&
                    typeof Symbol === 'function' &&
                    obj.constructor === Symbol &&
                    obj !== Symbol.prototype
                    ? 'symbol'
                    : typeof obj;
                };

          exports.shimLocalStreamsAPI = shimLocalStreamsAPI;
          exports.shimRemoteStreamsAPI = shimRemoteStreamsAPI;
          exports.shimCallbacksAPI = shimCallbacksAPI;
          exports.shimGetUserMedia = shimGetUserMedia;
          exports.shimConstraints = shimConstraints;
          exports.shimRTCIceServerUrls = shimRTCIceServerUrls;
          exports.shimTrackEventTransceiver = shimTrackEventTransceiver;
          exports.shimCreateOfferLegacy = shimCreateOfferLegacy;

          var _utils = require('../utils');

          var utils = _interopRequireWildcard(_utils);

          function _interopRequireWildcard(obj) {
            if (obj && obj.__esModule) {
              return obj;
            } else {
              var newObj = {};
              if (obj != null) {
                for (var key in obj) {
                  if (Object.prototype.hasOwnProperty.call(obj, key))
                    newObj[key] = obj[key];
                }
              }
              newObj.default = obj;
              return newObj;
            }
          }

          function shimLocalStreamsAPI(window) {
            if (
              (typeof window === 'undefined'
                ? 'undefined'
                : _typeof(window)) !== 'object' ||
              !window.RTCPeerConnection
            ) {
              return;
            }
            if (!('getLocalStreams' in window.RTCPeerConnection.prototype)) {
              window.RTCPeerConnection.prototype.getLocalStreams = function getLocalStreams() {
                if (!this._localStreams) {
                  this._localStreams = [];
                }
                return this._localStreams;
              };
            }
            if (!('addStream' in window.RTCPeerConnection.prototype)) {
              var _addTrack = window.RTCPeerConnection.prototype.addTrack;
              window.RTCPeerConnection.prototype.addStream = function addStream(
                stream
              ) {
                var _this = this;

                if (!this._localStreams) {
                  this._localStreams = [];
                }
                if (!this._localStreams.includes(stream)) {
                  this._localStreams.push(stream);
                }
                // Try to emulate Chrome's behaviour of adding in audio-video order.
                // Safari orders by track id.
                stream.getAudioTracks().forEach(function(track) {
                  return _addTrack.call(_this, track, stream);
                });
                stream.getVideoTracks().forEach(function(track) {
                  return _addTrack.call(_this, track, stream);
                });
              };

              window.RTCPeerConnection.prototype.addTrack = function addTrack(
                track
              ) {
                var stream = arguments[1];
                if (stream) {
                  if (!this._localStreams) {
                    this._localStreams = [stream];
                  } else if (!this._localStreams.includes(stream)) {
                    this._localStreams.push(stream);
                  }
                }
                return _addTrack.apply(this, arguments);
              };
            }
            if (!('removeStream' in window.RTCPeerConnection.prototype)) {
              window.RTCPeerConnection.prototype.removeStream = function removeStream(
                stream
              ) {
                var _this2 = this;

                if (!this._localStreams) {
                  this._localStreams = [];
                }
                var index = this._localStreams.indexOf(stream);
                if (index === -1) {
                  return;
                }
                this._localStreams.splice(index, 1);
                var tracks = stream.getTracks();
                this.getSenders().forEach(function(sender) {
                  if (tracks.includes(sender.track)) {
                    _this2.removeTrack(sender);
                  }
                });
              };
            }
          }

          function shimRemoteStreamsAPI(window) {
            if (
              (typeof window === 'undefined'
                ? 'undefined'
                : _typeof(window)) !== 'object' ||
              !window.RTCPeerConnection
            ) {
              return;
            }
            if (!('getRemoteStreams' in window.RTCPeerConnection.prototype)) {
              window.RTCPeerConnection.prototype.getRemoteStreams = function getRemoteStreams() {
                return this._remoteStreams ? this._remoteStreams : [];
              };
            }
            if (!('onaddstream' in window.RTCPeerConnection.prototype)) {
              Object.defineProperty(
                window.RTCPeerConnection.prototype,
                'onaddstream',
                {
                  get: function get() {
                    return this._onaddstream;
                  },
                  set: function set(f) {
                    var _this3 = this;

                    if (this._onaddstream) {
                      this.removeEventListener('addstream', this._onaddstream);
                      this.removeEventListener('track', this._onaddstreampoly);
                    }
                    this.addEventListener('addstream', (this._onaddstream = f));
                    this.addEventListener(
                      'track',
                      (this._onaddstreampoly = function(e) {
                        e.streams.forEach(function(stream) {
                          if (!_this3._remoteStreams) {
                            _this3._remoteStreams = [];
                          }
                          if (_this3._remoteStreams.includes(stream)) {
                            return;
                          }
                          _this3._remoteStreams.push(stream);
                          var event = new Event('addstream');
                          event.stream = stream;
                          _this3.dispatchEvent(event);
                        });
                      })
                    );
                  },
                }
              );
              var origSetRemoteDescription =
                window.RTCPeerConnection.prototype.setRemoteDescription;
              window.RTCPeerConnection.prototype.setRemoteDescription = function setRemoteDescription() {
                var pc = this;
                if (!this._onaddstreampoly) {
                  this.addEventListener(
                    'track',
                    (this._onaddstreampoly = function(e) {
                      e.streams.forEach(function(stream) {
                        if (!pc._remoteStreams) {
                          pc._remoteStreams = [];
                        }
                        if (pc._remoteStreams.indexOf(stream) >= 0) {
                          return;
                        }
                        pc._remoteStreams.push(stream);
                        var event = new Event('addstream');
                        event.stream = stream;
                        pc.dispatchEvent(event);
                      });
                    })
                  );
                }
                return origSetRemoteDescription.apply(pc, arguments);
              };
            }
          }

          function shimCallbacksAPI(window) {
            if (
              (typeof window === 'undefined'
                ? 'undefined'
                : _typeof(window)) !== 'object' ||
              !window.RTCPeerConnection
            ) {
              return;
            }
            var prototype = window.RTCPeerConnection.prototype;
            var origCreateOffer = prototype.createOffer;
            var origCreateAnswer = prototype.createAnswer;
            var setLocalDescription = prototype.setLocalDescription;
            var setRemoteDescription = prototype.setRemoteDescription;
            var addIceCandidate = prototype.addIceCandidate;

            prototype.createOffer = function createOffer(
              successCallback,
              failureCallback
            ) {
              var options = arguments.length >= 2 ? arguments[2] : arguments[0];
              var promise = origCreateOffer.apply(this, [options]);
              if (!failureCallback) {
                return promise;
              }
              promise.then(successCallback, failureCallback);
              return Promise.resolve();
            };

            prototype.createAnswer = function createAnswer(
              successCallback,
              failureCallback
            ) {
              var options = arguments.length >= 2 ? arguments[2] : arguments[0];
              var promise = origCreateAnswer.apply(this, [options]);
              if (!failureCallback) {
                return promise;
              }
              promise.then(successCallback, failureCallback);
              return Promise.resolve();
            };

            var withCallback = function withCallback(
              description,
              successCallback,
              failureCallback
            ) {
              var promise = setLocalDescription.apply(this, [description]);
              if (!failureCallback) {
                return promise;
              }
              promise.then(successCallback, failureCallback);
              return Promise.resolve();
            };
            prototype.setLocalDescription = withCallback;

            withCallback = function withCallback(
              description,
              successCallback,
              failureCallback
            ) {
              var promise = setRemoteDescription.apply(this, [description]);
              if (!failureCallback) {
                return promise;
              }
              promise.then(successCallback, failureCallback);
              return Promise.resolve();
            };
            prototype.setRemoteDescription = withCallback;

            withCallback = function withCallback(
              candidate,
              successCallback,
              failureCallback
            ) {
              var promise = addIceCandidate.apply(this, [candidate]);
              if (!failureCallback) {
                return promise;
              }
              promise.then(successCallback, failureCallback);
              return Promise.resolve();
            };
            prototype.addIceCandidate = withCallback;
          }

          function shimGetUserMedia(window) {
            var navigator = window && window.navigator;

            if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
              // shim not needed in Safari 12.1
              var mediaDevices = navigator.mediaDevices;
              var _getUserMedia = mediaDevices.getUserMedia.bind(mediaDevices);
              navigator.mediaDevices.getUserMedia = function(constraints) {
                return _getUserMedia(shimConstraints(constraints));
              };
            }

            if (
              !navigator.getUserMedia &&
              navigator.mediaDevices &&
              navigator.mediaDevices.getUserMedia
            ) {
              navigator.getUserMedia = function getUserMedia(
                constraints,
                cb,
                errcb
              ) {
                navigator.mediaDevices
                  .getUserMedia(constraints)
                  .then(cb, errcb);
              }.bind(navigator);
            }
          }

          function shimConstraints(constraints) {
            if (constraints && constraints.video !== undefined) {
              return Object.assign({}, constraints, {
                video: utils.compactObject(constraints.video),
              });
            }

            return constraints;
          }

          function shimRTCIceServerUrls(window) {
            // migrate from non-spec RTCIceServer.url to RTCIceServer.urls
            var OrigPeerConnection = window.RTCPeerConnection;
            window.RTCPeerConnection = function RTCPeerConnection(
              pcConfig,
              pcConstraints
            ) {
              if (pcConfig && pcConfig.iceServers) {
                var newIceServers = [];
                for (var i = 0; i < pcConfig.iceServers.length; i++) {
                  var server = pcConfig.iceServers[i];
                  if (
                    !server.hasOwnProperty('urls') &&
                    server.hasOwnProperty('url')
                  ) {
                    utils.deprecated('RTCIceServer.url', 'RTCIceServer.urls');
                    server = JSON.parse(JSON.stringify(server));
                    server.urls = server.url;
                    delete server.url;
                    newIceServers.push(server);
                  } else {
                    newIceServers.push(pcConfig.iceServers[i]);
                  }
                }
                pcConfig.iceServers = newIceServers;
              }
              return new OrigPeerConnection(pcConfig, pcConstraints);
            };
            window.RTCPeerConnection.prototype = OrigPeerConnection.prototype;
            // wrap static methods. Currently just generateCertificate.
            if ('generateCertificate' in window.RTCPeerConnection) {
              Object.defineProperty(
                window.RTCPeerConnection,
                'generateCertificate',
                {
                  get: function get() {
                    return OrigPeerConnection.generateCertificate;
                  },
                }
              );
            }
          }

          function shimTrackEventTransceiver(window) {
            // Add event.transceiver member over deprecated event.receiver
            if (
              (typeof window === 'undefined'
                ? 'undefined'
                : _typeof(window)) === 'object' &&
              window.RTCTrackEvent &&
              'receiver' in window.RTCTrackEvent.prototype &&
              !('transceiver' in window.RTCTrackEvent.prototype)
            ) {
              Object.defineProperty(
                window.RTCTrackEvent.prototype,
                'transceiver',
                {
                  get: function get() {
                    return { receiver: this.receiver };
                  },
                }
              );
            }
          }

          function shimCreateOfferLegacy(window) {
            var origCreateOffer =
              window.RTCPeerConnection.prototype.createOffer;
            window.RTCPeerConnection.prototype.createOffer = function createOffer(
              offerOptions
            ) {
              if (offerOptions) {
                if (typeof offerOptions.offerToReceiveAudio !== 'undefined') {
                  // support bit values
                  offerOptions.offerToReceiveAudio = !!offerOptions.offerToReceiveAudio;
                }
                var audioTransceiver = this.getTransceivers().find(function(
                  transceiver
                ) {
                  return transceiver.receiver.track.kind === 'audio';
                });
                if (
                  offerOptions.offerToReceiveAudio === false &&
                  audioTransceiver
                ) {
                  if (audioTransceiver.direction === 'sendrecv') {
                    if (audioTransceiver.setDirection) {
                      audioTransceiver.setDirection('sendonly');
                    } else {
                      audioTransceiver.direction = 'sendonly';
                    }
                  } else if (audioTransceiver.direction === 'recvonly') {
                    if (audioTransceiver.setDirection) {
                      audioTransceiver.setDirection('inactive');
                    } else {
                      audioTransceiver.direction = 'inactive';
                    }
                  }
                } else if (
                  offerOptions.offerToReceiveAudio === true &&
                  !audioTransceiver
                ) {
                  this.addTransceiver('audio');
                }

                if (typeof offerOptions.offerToReceiveVideo !== 'undefined') {
                  // support bit values
                  offerOptions.offerToReceiveVideo = !!offerOptions.offerToReceiveVideo;
                }
                var videoTransceiver = this.getTransceivers().find(function(
                  transceiver
                ) {
                  return transceiver.receiver.track.kind === 'video';
                });
                if (
                  offerOptions.offerToReceiveVideo === false &&
                  videoTransceiver
                ) {
                  if (videoTransceiver.direction === 'sendrecv') {
                    if (videoTransceiver.setDirection) {
                      videoTransceiver.setDirection('sendonly');
                    } else {
                      videoTransceiver.direction = 'sendonly';
                    }
                  } else if (videoTransceiver.direction === 'recvonly') {
                    if (videoTransceiver.setDirection) {
                      videoTransceiver.setDirection('inactive');
                    } else {
                      videoTransceiver.direction = 'inactive';
                    }
                  }
                } else if (
                  offerOptions.offerToReceiveVideo === true &&
                  !videoTransceiver
                ) {
                  this.addTransceiver('video');
                }
              }
              return origCreateOffer.apply(this, arguments);
            };
          }
        },
        { '../utils': 15 },
      ],
      15: [
        function(require, module, exports) {
          /*
           *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
           *
           *  Use of this source code is governed by a BSD-style license
           *  that can be found in the LICENSE file in the root of the source
           *  tree.
           */
          /* eslint-env node */
          'use strict';

          Object.defineProperty(exports, '__esModule', {
            value: true,
          });

          var _typeof =
            typeof Symbol === 'function' && typeof Symbol.iterator === 'symbol'
              ? function(obj) {
                  return typeof obj;
                }
              : function(obj) {
                  return obj &&
                    typeof Symbol === 'function' &&
                    obj.constructor === Symbol &&
                    obj !== Symbol.prototype
                    ? 'symbol'
                    : typeof obj;
                };

          exports.extractVersion = extractVersion;
          exports.wrapPeerConnectionEvent = wrapPeerConnectionEvent;
          exports.disableLog = disableLog;
          exports.disableWarnings = disableWarnings;
          exports.log = log;
          exports.deprecated = deprecated;
          exports.detectBrowser = detectBrowser;
          exports.compactObject = compactObject;
          exports.walkStats = walkStats;
          exports.filterStats = filterStats;

          function _defineProperty(obj, key, value) {
            if (key in obj) {
              Object.defineProperty(obj, key, {
                value: value,
                enumerable: true,
                configurable: true,
                writable: true,
              });
            } else {
              obj[key] = value;
            }
            return obj;
          }

          var logDisabled_ = true;
          var deprecationWarnings_ = true;

          /**
           * Extract browser version out of the provided user agent string.
           *
           * @param {!string} uastring userAgent string.
           * @param {!string} expr Regular expression used as match criteria.
           * @param {!number} pos position in the version string to be returned.
           * @return {!number} browser version.
           */
          function extractVersion(uastring, expr, pos) {
            var match = uastring.match(expr);
            return match && match.length >= pos && parseInt(match[pos], 10);
          }

          // Wraps the peerconnection event eventNameToWrap in a function
          // which returns the modified event object (or false to prevent
          // the event).
          function wrapPeerConnectionEvent(window, eventNameToWrap, wrapper) {
            if (!window.RTCPeerConnection) {
              return;
            }
            var proto = window.RTCPeerConnection.prototype;
            var nativeAddEventListener = proto.addEventListener;
            proto.addEventListener = function(nativeEventName, cb) {
              if (nativeEventName !== eventNameToWrap) {
                return nativeAddEventListener.apply(this, arguments);
              }
              var wrappedCallback = function wrappedCallback(e) {
                var modifiedEvent = wrapper(e);
                if (modifiedEvent) {
                  cb(modifiedEvent);
                }
              };
              this._eventMap = this._eventMap || {};
              this._eventMap[cb] = wrappedCallback;
              return nativeAddEventListener.apply(this, [
                nativeEventName,
                wrappedCallback,
              ]);
            };

            var nativeRemoveEventListener = proto.removeEventListener;
            proto.removeEventListener = function(nativeEventName, cb) {
              if (
                nativeEventName !== eventNameToWrap ||
                !this._eventMap ||
                !this._eventMap[cb]
              ) {
                return nativeRemoveEventListener.apply(this, arguments);
              }
              var unwrappedCb = this._eventMap[cb];
              delete this._eventMap[cb];
              return nativeRemoveEventListener.apply(this, [
                nativeEventName,
                unwrappedCb,
              ]);
            };

            Object.defineProperty(proto, 'on' + eventNameToWrap, {
              get: function get() {
                return this['_on' + eventNameToWrap];
              },
              set: function set(cb) {
                if (this['_on' + eventNameToWrap]) {
                  this.removeEventListener(
                    eventNameToWrap,
                    this['_on' + eventNameToWrap]
                  );
                  delete this['_on' + eventNameToWrap];
                }
                if (cb) {
                  this.addEventListener(
                    eventNameToWrap,
                    (this['_on' + eventNameToWrap] = cb)
                  );
                }
              },

              enumerable: true,
              configurable: true,
            });
          }

          function disableLog(bool) {
            if (typeof bool !== 'boolean') {
              return new Error(
                'Argument type: ' +
                  (typeof bool === 'undefined' ? 'undefined' : _typeof(bool)) +
                  '. Please use a boolean.'
              );
            }
            logDisabled_ = bool;
            return bool
              ? 'adapter.js logging disabled'
              : 'adapter.js logging enabled';
          }

          /**
           * Disable or enable deprecation warnings
           * @param {!boolean} bool set to true to disable warnings.
           */
          function disableWarnings(bool) {
            if (typeof bool !== 'boolean') {
              return new Error(
                'Argument type: ' +
                  (typeof bool === 'undefined' ? 'undefined' : _typeof(bool)) +
                  '. Please use a boolean.'
              );
            }
            deprecationWarnings_ = !bool;
            return (
              'adapter.js deprecation warnings ' +
              (bool ? 'disabled' : 'enabled')
            );
          }

          function log() {
            if (
              (typeof window === 'undefined'
                ? 'undefined'
                : _typeof(window)) === 'object'
            ) {
              if (logDisabled_) {
                return;
              }
              if (
                typeof console !== 'undefined' &&
                typeof console.log === 'function'
              ) {
                console.log.apply(console, arguments);
              }
            }
          }

          /**
           * Shows a deprecation warning suggesting the modern and spec-compatible API.
           */
          function deprecated(oldMethod, newMethod) {
            if (!deprecationWarnings_) {
              return;
            }
            console.warn(
              oldMethod +
                ' is deprecated, please use ' +
                newMethod +
                ' instead.'
            );
          }

          /**
           * Browser detector.
           *
           * @return {object} result containing browser and version
           *     properties.
           */
          function detectBrowser(window) {
            var navigator = window.navigator;

            // Returned result object.

            var result = { browser: null, version: null };

            // Fail early if it's not a browser
            if (typeof window === 'undefined' || !window.navigator) {
              result.browser = 'Not a browser.';
              return result;
            }

            if (navigator.mozGetUserMedia) {
              // Firefox.
              result.browser = 'firefox';
              result.version = extractVersion(
                navigator.userAgent,
                /Firefox\/(\d+)\./,
                1
              );
            } else if (
              navigator.webkitGetUserMedia ||
              (window.isSecureContext === false &&
                window.webkitRTCPeerConnection &&
                !window.RTCIceGatherer)
            ) {
              // Chrome, Chromium, Webview, Opera.
              // Version matches Chrome/WebRTC version.
              // Chrome 74 removed webkitGetUserMedia on http as well so we need the
              // more complicated fallback to webkitRTCPeerConnection.
              result.browser = 'chrome';
              result.version = extractVersion(
                navigator.userAgent,
                /Chrom(e|ium)\/(\d+)\./,
                2
              );
            } else if (
              navigator.mediaDevices &&
              navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)
            ) {
              // Edge.
              result.browser = 'edge';
              result.version = extractVersion(
                navigator.userAgent,
                /Edge\/(\d+).(\d+)$/,
                2
              );
            } else if (
              window.RTCPeerConnection &&
              navigator.userAgent.match(/AppleWebKit\/(\d+)\./)
            ) {
              // Safari.
              result.browser = 'safari';
              result.version = extractVersion(
                navigator.userAgent,
                /AppleWebKit\/(\d+)\./,
                1
              );
              result.supportsUnifiedPlan =
                window.RTCRtpTransceiver &&
                'currentDirection' in window.RTCRtpTransceiver.prototype;
            } else {
              // Default fallthrough: not supported.
              result.browser = 'Not a supported browser.';
              return result;
            }

            return result;
          }

          /**
           * Checks if something is an object.
           *
           * @param {*} val The something you want to check.
           * @return true if val is an object, false otherwise.
           */
          function isObject(val) {
            return Object.prototype.toString.call(val) === '[object Object]';
          }

          /**
           * Remove all empty objects and undefined values
           * from a nested object -- an enhanced and vanilla version
           * of Lodash's `compact`.
           */
          function compactObject(data) {
            if (!isObject(data)) {
              return data;
            }

            return Object.keys(data).reduce(function(accumulator, key) {
              var isObj = isObject(data[key]);
              var value = isObj ? compactObject(data[key]) : data[key];
              var isEmptyObject = isObj && !Object.keys(value).length;
              if (value === undefined || isEmptyObject) {
                return accumulator;
              }
              return Object.assign(
                accumulator,
                _defineProperty({}, key, value)
              );
            }, {});
          }

          /* iterates the stats graph recursively. */
          function walkStats(stats, base, resultSet) {
            if (!base || resultSet.has(base.id)) {
              return;
            }
            resultSet.set(base.id, base);
            Object.keys(base).forEach(function(name) {
              if (name.endsWith('Id')) {
                walkStats(stats, stats.get(base[name]), resultSet);
              } else if (name.endsWith('Ids')) {
                base[name].forEach(function(id) {
                  walkStats(stats, stats.get(id), resultSet);
                });
              }
            });
          }

          /* filter getStats for a sender/receiver track. */
          function filterStats(result, track, outbound) {
            var streamStatsType = outbound ? 'outbound-rtp' : 'inbound-rtp';
            var filteredResult = new Map();
            if (track === null) {
              return filteredResult;
            }
            var trackStats = [];
            result.forEach(function(value) {
              if (
                value.type === 'track' &&
                value.trackIdentifier === track.id
              ) {
                trackStats.push(value);
              }
            });
            trackStats.forEach(function(trackStat) {
              result.forEach(function(stats) {
                if (
                  stats.type === streamStatsType &&
                  stats.trackId === trackStat.id
                ) {
                  walkStats(result, stats, filteredResult);
                }
              });
            });
            return filteredResult;
          }
        },
        {},
      ],
      16: [
        function(require, module, exports) {
          /*
           *  Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
           *
           *  Use of this source code is governed by a BSD-style license
           *  that can be found in the LICENSE file in the root of the source
           *  tree.
           */
          /* eslint-env node */
          'use strict';

          var SDPUtils = require('sdp');

          function fixStatsType(stat) {
            return (
              {
                inboundrtp: 'inbound-rtp',
                outboundrtp: 'outbound-rtp',
                candidatepair: 'candidate-pair',
                localcandidate: 'local-candidate',
                remotecandidate: 'remote-candidate',
              }[stat.type] || stat.type
            );
          }

          function writeMediaSection(
            transceiver,
            caps,
            type,
            stream,
            dtlsRole
          ) {
            var sdp = SDPUtils.writeRtpDescription(transceiver.kind, caps);

            // Map ICE parameters (ufrag, pwd) to SDP.
            sdp += SDPUtils.writeIceParameters(
              transceiver.iceGatherer.getLocalParameters()
            );

            // Map DTLS parameters to SDP.
            sdp += SDPUtils.writeDtlsParameters(
              transceiver.dtlsTransport.getLocalParameters(),
              type === 'offer' ? 'actpass' : dtlsRole || 'active'
            );

            sdp += 'a=mid:' + transceiver.mid + '\r\n';

            if (transceiver.rtpSender && transceiver.rtpReceiver) {
              sdp += 'a=sendrecv\r\n';
            } else if (transceiver.rtpSender) {
              sdp += 'a=sendonly\r\n';
            } else if (transceiver.rtpReceiver) {
              sdp += 'a=recvonly\r\n';
            } else {
              sdp += 'a=inactive\r\n';
            }

            if (transceiver.rtpSender) {
              var trackId =
                transceiver.rtpSender._initialTrackId ||
                transceiver.rtpSender.track.id;
              transceiver.rtpSender._initialTrackId = trackId;
              // spec.
              var msid =
                'msid:' + (stream ? stream.id : '-') + ' ' + trackId + '\r\n';
              sdp += 'a=' + msid;
              // for Chrome. Legacy should no longer be required.
              sdp +=
                'a=ssrc:' +
                transceiver.sendEncodingParameters[0].ssrc +
                ' ' +
                msid;

              // RTX
              if (transceiver.sendEncodingParameters[0].rtx) {
                sdp +=
                  'a=ssrc:' +
                  transceiver.sendEncodingParameters[0].rtx.ssrc +
                  ' ' +
                  msid;
                sdp +=
                  'a=ssrc-group:FID ' +
                  transceiver.sendEncodingParameters[0].ssrc +
                  ' ' +
                  transceiver.sendEncodingParameters[0].rtx.ssrc +
                  '\r\n';
              }
            }
            // FIXME: this should be written by writeRtpDescription.
            sdp +=
              'a=ssrc:' +
              transceiver.sendEncodingParameters[0].ssrc +
              ' cname:' +
              SDPUtils.localCName +
              '\r\n';
            if (
              transceiver.rtpSender &&
              transceiver.sendEncodingParameters[0].rtx
            ) {
              sdp +=
                'a=ssrc:' +
                transceiver.sendEncodingParameters[0].rtx.ssrc +
                ' cname:' +
                SDPUtils.localCName +
                '\r\n';
            }
            return sdp;
          }

          // Edge does not like
          // 1) stun: filtered after 14393 unless ?transport=udp is present
          // 2) turn: that does not have all of turn:host:port?transport=udp
          // 3) turn: with ipv6 addresses
          // 4) turn: occurring muliple times
          function filterIceServers(iceServers, edgeVersion) {
            var hasTurn = false;
            iceServers = JSON.parse(JSON.stringify(iceServers));
            return iceServers.filter(function(server) {
              if (server && (server.urls || server.url)) {
                var urls = server.urls || server.url;
                if (server.url && !server.urls) {
                  console.warn(
                    'RTCIceServer.url is deprecated! Use urls instead.'
                  );
                }
                var isString = typeof urls === 'string';
                if (isString) {
                  urls = [urls];
                }
                urls = urls.filter(function(url) {
                  var validTurn =
                    url.indexOf('turn:') === 0 &&
                    url.indexOf('transport=udp') !== -1 &&
                    url.indexOf('turn:[') === -1 &&
                    !hasTurn;

                  if (validTurn) {
                    hasTurn = true;
                    return true;
                  }
                  return (
                    url.indexOf('stun:') === 0 &&
                    edgeVersion >= 14393 &&
                    url.indexOf('?transport=udp') === -1
                  );
                });

                delete server.url;
                server.urls = isString ? urls[0] : urls;
                return !!urls.length;
              }
            });
          }

          // Determines the intersection of local and remote capabilities.
          function getCommonCapabilities(
            localCapabilities,
            remoteCapabilities
          ) {
            var commonCapabilities = {
              codecs: [],
              headerExtensions: [],
              fecMechanisms: [],
            };

            var findCodecByPayloadType = function(pt, codecs) {
              pt = parseInt(pt, 10);
              for (var i = 0; i < codecs.length; i++) {
                if (
                  codecs[i].payloadType === pt ||
                  codecs[i].preferredPayloadType === pt
                ) {
                  return codecs[i];
                }
              }
            };

            var rtxCapabilityMatches = function(lRtx, rRtx, lCodecs, rCodecs) {
              var lCodec = findCodecByPayloadType(lRtx.parameters.apt, lCodecs);
              var rCodec = findCodecByPayloadType(rRtx.parameters.apt, rCodecs);
              return (
                lCodec &&
                rCodec &&
                lCodec.name.toLowerCase() === rCodec.name.toLowerCase()
              );
            };

            localCapabilities.codecs.forEach(function(lCodec) {
              for (var i = 0; i < remoteCapabilities.codecs.length; i++) {
                var rCodec = remoteCapabilities.codecs[i];
                if (
                  lCodec.name.toLowerCase() === rCodec.name.toLowerCase() &&
                  lCodec.clockRate === rCodec.clockRate
                ) {
                  if (
                    lCodec.name.toLowerCase() === 'rtx' &&
                    lCodec.parameters &&
                    rCodec.parameters.apt
                  ) {
                    // for RTX we need to find the local rtx that has a apt
                    // which points to the same local codec as the remote one.
                    if (
                      !rtxCapabilityMatches(
                        lCodec,
                        rCodec,
                        localCapabilities.codecs,
                        remoteCapabilities.codecs
                      )
                    ) {
                      continue;
                    }
                  }
                  rCodec = JSON.parse(JSON.stringify(rCodec)); // deepcopy
                  // number of channels is the highest common number of channels
                  rCodec.numChannels = Math.min(
                    lCodec.numChannels,
                    rCodec.numChannels
                  );
                  // push rCodec so we reply with offerer payload type
                  commonCapabilities.codecs.push(rCodec);

                  // determine common feedback mechanisms
                  rCodec.rtcpFeedback = rCodec.rtcpFeedback.filter(function(
                    fb
                  ) {
                    for (var j = 0; j < lCodec.rtcpFeedback.length; j++) {
                      if (
                        lCodec.rtcpFeedback[j].type === fb.type &&
                        lCodec.rtcpFeedback[j].parameter === fb.parameter
                      ) {
                        return true;
                      }
                    }
                    return false;
                  });
                  // FIXME: also need to determine .parameters
                  //  see https://github.com/openpeer/ortc/issues/569
                  break;
                }
              }
            });

            localCapabilities.headerExtensions.forEach(function(
              lHeaderExtension
            ) {
              for (
                var i = 0;
                i < remoteCapabilities.headerExtensions.length;
                i++
              ) {
                var rHeaderExtension = remoteCapabilities.headerExtensions[i];
                if (lHeaderExtension.uri === rHeaderExtension.uri) {
                  commonCapabilities.headerExtensions.push(rHeaderExtension);
                  break;
                }
              }
            });

            // FIXME: fecMechanisms
            return commonCapabilities;
          }

          // is action=setLocalDescription with type allowed in signalingState
          function isActionAllowedInSignalingState(
            action,
            type,
            signalingState
          ) {
            return (
              {
                offer: {
                  setLocalDescription: ['stable', 'have-local-offer'],
                  setRemoteDescription: ['stable', 'have-remote-offer'],
                },
                answer: {
                  setLocalDescription: [
                    'have-remote-offer',
                    'have-local-pranswer',
                  ],
                  setRemoteDescription: [
                    'have-local-offer',
                    'have-remote-pranswer',
                  ],
                },
              }[type][action].indexOf(signalingState) !== -1
            );
          }

          function maybeAddCandidate(iceTransport, candidate) {
            // Edge's internal representation adds some fields therefore
            // not all fieldѕ are taken into account.
            var alreadyAdded = iceTransport
              .getRemoteCandidates()
              .find(function(remoteCandidate) {
                return (
                  candidate.foundation === remoteCandidate.foundation &&
                  candidate.ip === remoteCandidate.ip &&
                  candidate.port === remoteCandidate.port &&
                  candidate.priority === remoteCandidate.priority &&
                  candidate.protocol === remoteCandidate.protocol &&
                  candidate.type === remoteCandidate.type
                );
              });
            if (!alreadyAdded) {
              iceTransport.addRemoteCandidate(candidate);
            }
            return !alreadyAdded;
          }

          function makeError(name, description) {
            var e = new Error(description);
            e.name = name;
            // legacy error codes from https://heycam.github.io/webidl/#idl-DOMException-error-names
            e.code = {
              NotSupportedError: 9,
              InvalidStateError: 11,
              InvalidAccessError: 15,
              TypeError: undefined,
              OperationError: undefined,
            }[name];
            return e;
          }

          module.exports = function(window, edgeVersion) {
            // https://w3c.github.io/mediacapture-main/#mediastream
            // Helper function to add the track to the stream and
            // dispatch the event ourselves.
            function addTrackToStreamAndFireEvent(track, stream) {
              stream.addTrack(track);
              stream.dispatchEvent(
                new window.MediaStreamTrackEvent('addtrack', { track: track })
              );
            }

            function removeTrackFromStreamAndFireEvent(track, stream) {
              stream.removeTrack(track);
              stream.dispatchEvent(
                new window.MediaStreamTrackEvent('removetrack', {
                  track: track,
                })
              );
            }

            function fireAddTrack(pc, track, receiver, streams) {
              var trackEvent = new Event('track');
              trackEvent.track = track;
              trackEvent.receiver = receiver;
              trackEvent.transceiver = { receiver: receiver };
              trackEvent.streams = streams;
              window.setTimeout(function() {
                pc._dispatchEvent('track', trackEvent);
              });
            }

            var RTCPeerConnection = function(config) {
              var pc = this;

              var _eventTarget = document.createDocumentFragment();
              [
                'addEventListener',
                'removeEventListener',
                'dispatchEvent',
              ].forEach(function(method) {
                pc[method] = _eventTarget[method].bind(_eventTarget);
              });

              this.canTrickleIceCandidates = null;

              this.needNegotiation = false;

              this.localStreams = [];
              this.remoteStreams = [];

              this._localDescription = null;
              this._remoteDescription = null;

              this.signalingState = 'stable';
              this.iceConnectionState = 'new';
              this.connectionState = 'new';
              this.iceGatheringState = 'new';

              config = JSON.parse(JSON.stringify(config || {}));

              this.usingBundle = config.bundlePolicy === 'max-bundle';
              if (config.rtcpMuxPolicy === 'negotiate') {
                throw makeError(
                  'NotSupportedError',
                  "rtcpMuxPolicy 'negotiate' is not supported"
                );
              } else if (!config.rtcpMuxPolicy) {
                config.rtcpMuxPolicy = 'require';
              }

              switch (config.iceTransportPolicy) {
                case 'all':
                case 'relay':
                  break;
                default:
                  config.iceTransportPolicy = 'all';
                  break;
              }

              switch (config.bundlePolicy) {
                case 'balanced':
                case 'max-compat':
                case 'max-bundle':
                  break;
                default:
                  config.bundlePolicy = 'balanced';
                  break;
              }

              config.iceServers = filterIceServers(
                config.iceServers || [],
                edgeVersion
              );

              this._iceGatherers = [];
              if (config.iceCandidatePoolSize) {
                for (var i = config.iceCandidatePoolSize; i > 0; i--) {
                  this._iceGatherers.push(
                    new window.RTCIceGatherer({
                      iceServers: config.iceServers,
                      gatherPolicy: config.iceTransportPolicy,
                    })
                  );
                }
              } else {
                config.iceCandidatePoolSize = 0;
              }

              this._config = config;

              // per-track iceGathers, iceTransports, dtlsTransports, rtpSenders, ...
              // everything that is needed to describe a SDP m-line.
              this.transceivers = [];

              this._sdpSessionId = SDPUtils.generateSessionId();
              this._sdpSessionVersion = 0;

              this._dtlsRole = undefined; // role for a=setup to use in answers.

              this._isClosed = false;
            };

            Object.defineProperty(
              RTCPeerConnection.prototype,
              'localDescription',
              {
                configurable: true,
                get: function() {
                  return this._localDescription;
                },
              }
            );
            Object.defineProperty(
              RTCPeerConnection.prototype,
              'remoteDescription',
              {
                configurable: true,
                get: function() {
                  return this._remoteDescription;
                },
              }
            );

            // set up event handlers on prototype
            RTCPeerConnection.prototype.onicecandidate = null;
            RTCPeerConnection.prototype.onaddstream = null;
            RTCPeerConnection.prototype.ontrack = null;
            RTCPeerConnection.prototype.onremovestream = null;
            RTCPeerConnection.prototype.onsignalingstatechange = null;
            RTCPeerConnection.prototype.oniceconnectionstatechange = null;
            RTCPeerConnection.prototype.onconnectionstatechange = null;
            RTCPeerConnection.prototype.onicegatheringstatechange = null;
            RTCPeerConnection.prototype.onnegotiationneeded = null;
            RTCPeerConnection.prototype.ondatachannel = null;

            RTCPeerConnection.prototype._dispatchEvent = function(name, event) {
              if (this._isClosed) {
                return;
              }
              this.dispatchEvent(event);
              if (typeof this['on' + name] === 'function') {
                this['on' + name](event);
              }
            };

            RTCPeerConnection.prototype._emitGatheringStateChange = function() {
              var event = new Event('icegatheringstatechange');
              this._dispatchEvent('icegatheringstatechange', event);
            };

            RTCPeerConnection.prototype.getConfiguration = function() {
              return this._config;
            };

            RTCPeerConnection.prototype.getLocalStreams = function() {
              return this.localStreams;
            };

            RTCPeerConnection.prototype.getRemoteStreams = function() {
              return this.remoteStreams;
            };

            // internal helper to create a transceiver object.
            // (which is not yet the same as the WebRTC 1.0 transceiver)
            RTCPeerConnection.prototype._createTransceiver = function(
              kind,
              doNotAdd
            ) {
              var hasBundleTransport = this.transceivers.length > 0;
              var transceiver = {
                track: null,
                iceGatherer: null,
                iceTransport: null,
                dtlsTransport: null,
                localCapabilities: null,
                remoteCapabilities: null,
                rtpSender: null,
                rtpReceiver: null,
                kind: kind,
                mid: null,
                sendEncodingParameters: null,
                recvEncodingParameters: null,
                stream: null,
                associatedRemoteMediaStreams: [],
                wantReceive: true,
              };
              if (this.usingBundle && hasBundleTransport) {
                transceiver.iceTransport = this.transceivers[0].iceTransport;
                transceiver.dtlsTransport = this.transceivers[0].dtlsTransport;
              } else {
                var transports = this._createIceAndDtlsTransports();
                transceiver.iceTransport = transports.iceTransport;
                transceiver.dtlsTransport = transports.dtlsTransport;
              }
              if (!doNotAdd) {
                this.transceivers.push(transceiver);
              }
              return transceiver;
            };

            RTCPeerConnection.prototype.addTrack = function(track, stream) {
              if (this._isClosed) {
                throw makeError(
                  'InvalidStateError',
                  'Attempted to call addTrack on a closed peerconnection.'
                );
              }

              var alreadyExists = this.transceivers.find(function(s) {
                return s.track === track;
              });

              if (alreadyExists) {
                throw makeError('InvalidAccessError', 'Track already exists.');
              }

              var transceiver;
              for (var i = 0; i < this.transceivers.length; i++) {
                if (
                  !this.transceivers[i].track &&
                  this.transceivers[i].kind === track.kind
                ) {
                  transceiver = this.transceivers[i];
                }
              }
              if (!transceiver) {
                transceiver = this._createTransceiver(track.kind);
              }

              this._maybeFireNegotiationNeeded();

              if (this.localStreams.indexOf(stream) === -1) {
                this.localStreams.push(stream);
              }

              transceiver.track = track;
              transceiver.stream = stream;
              transceiver.rtpSender = new window.RTCRtpSender(
                track,
                transceiver.dtlsTransport
              );
              return transceiver.rtpSender;
            };

            RTCPeerConnection.prototype.addStream = function(stream) {
              var pc = this;
              if (edgeVersion >= 15025) {
                stream.getTracks().forEach(function(track) {
                  pc.addTrack(track, stream);
                });
              } else {
                // Clone is necessary for local demos mostly, attaching directly
                // to two different senders does not work (build 10547).
                // Fixed in 15025 (or earlier)
                var clonedStream = stream.clone();
                stream.getTracks().forEach(function(track, idx) {
                  var clonedTrack = clonedStream.getTracks()[idx];
                  track.addEventListener('enabled', function(event) {
                    clonedTrack.enabled = event.enabled;
                  });
                });
                clonedStream.getTracks().forEach(function(track) {
                  pc.addTrack(track, clonedStream);
                });
              }
            };

            RTCPeerConnection.prototype.removeTrack = function(sender) {
              if (this._isClosed) {
                throw makeError(
                  'InvalidStateError',
                  'Attempted to call removeTrack on a closed peerconnection.'
                );
              }

              if (!(sender instanceof window.RTCRtpSender)) {
                throw new TypeError(
                  'Argument 1 of RTCPeerConnection.removeTrack ' +
                    'does not implement interface RTCRtpSender.'
                );
              }

              var transceiver = this.transceivers.find(function(t) {
                return t.rtpSender === sender;
              });

              if (!transceiver) {
                throw makeError(
                  'InvalidAccessError',
                  'Sender was not created by this connection.'
                );
              }
              var stream = transceiver.stream;

              transceiver.rtpSender.stop();
              transceiver.rtpSender = null;
              transceiver.track = null;
              transceiver.stream = null;

              // remove the stream from the set of local streams
              var localStreams = this.transceivers.map(function(t) {
                return t.stream;
              });
              if (
                localStreams.indexOf(stream) === -1 &&
                this.localStreams.indexOf(stream) > -1
              ) {
                this.localStreams.splice(this.localStreams.indexOf(stream), 1);
              }

              this._maybeFireNegotiationNeeded();
            };

            RTCPeerConnection.prototype.removeStream = function(stream) {
              var pc = this;
              stream.getTracks().forEach(function(track) {
                var sender = pc.getSenders().find(function(s) {
                  return s.track === track;
                });
                if (sender) {
                  pc.removeTrack(sender);
                }
              });
            };

            RTCPeerConnection.prototype.getSenders = function() {
              return this.transceivers
                .filter(function(transceiver) {
                  return !!transceiver.rtpSender;
                })
                .map(function(transceiver) {
                  return transceiver.rtpSender;
                });
            };

            RTCPeerConnection.prototype.getReceivers = function() {
              return this.transceivers
                .filter(function(transceiver) {
                  return !!transceiver.rtpReceiver;
                })
                .map(function(transceiver) {
                  return transceiver.rtpReceiver;
                });
            };

            RTCPeerConnection.prototype._createIceGatherer = function(
              sdpMLineIndex,
              usingBundle
            ) {
              var pc = this;
              if (usingBundle && sdpMLineIndex > 0) {
                return this.transceivers[0].iceGatherer;
              } else if (this._iceGatherers.length) {
                return this._iceGatherers.shift();
              }
              var iceGatherer = new window.RTCIceGatherer({
                iceServers: this._config.iceServers,
                gatherPolicy: this._config.iceTransportPolicy,
              });
              Object.defineProperty(iceGatherer, 'state', {
                value: 'new',
                writable: true,
              });

              this.transceivers[sdpMLineIndex].bufferedCandidateEvents = [];
              this.transceivers[sdpMLineIndex].bufferCandidates = function(
                event
              ) {
                var end =
                  !event.candidate || Object.keys(event.candidate).length === 0;
                // polyfill since RTCIceGatherer.state is not implemented in
                // Edge 10547 yet.
                iceGatherer.state = end ? 'completed' : 'gathering';
                if (
                  pc.transceivers[sdpMLineIndex].bufferedCandidateEvents !==
                  null
                ) {
                  pc.transceivers[sdpMLineIndex].bufferedCandidateEvents.push(
                    event
                  );
                }
              };
              iceGatherer.addEventListener(
                'localcandidate',
                this.transceivers[sdpMLineIndex].bufferCandidates
              );
              return iceGatherer;
            };

            // start gathering from an RTCIceGatherer.
            RTCPeerConnection.prototype._gather = function(mid, sdpMLineIndex) {
              var pc = this;
              var iceGatherer = this.transceivers[sdpMLineIndex].iceGatherer;
              if (iceGatherer.onlocalcandidate) {
                return;
              }
              var bufferedCandidateEvents = this.transceivers[sdpMLineIndex]
                .bufferedCandidateEvents;
              this.transceivers[sdpMLineIndex].bufferedCandidateEvents = null;
              iceGatherer.removeEventListener(
                'localcandidate',
                this.transceivers[sdpMLineIndex].bufferCandidates
              );
              iceGatherer.onlocalcandidate = function(evt) {
                if (pc.usingBundle && sdpMLineIndex > 0) {
                  // if we know that we use bundle we can drop candidates with
                  // ѕdpMLineIndex > 0. If we don't do this then our state gets
                  // confused since we dispose the extra ice gatherer.
                  return;
                }
                var event = new Event('icecandidate');
                event.candidate = { sdpMid: mid, sdpMLineIndex: sdpMLineIndex };

                var cand = evt.candidate;
                // Edge emits an empty object for RTCIceCandidateComplete‥
                var end = !cand || Object.keys(cand).length === 0;
                if (end) {
                  // polyfill since RTCIceGatherer.state is not implemented in
                  // Edge 10547 yet.
                  if (
                    iceGatherer.state === 'new' ||
                    iceGatherer.state === 'gathering'
                  ) {
                    iceGatherer.state = 'completed';
                  }
                } else {
                  if (iceGatherer.state === 'new') {
                    iceGatherer.state = 'gathering';
                  }
                  // RTCIceCandidate doesn't have a component, needs to be added
                  cand.component = 1;
                  // also the usernameFragment. TODO: update SDP to take both variants.
                  cand.ufrag = iceGatherer.getLocalParameters().usernameFragment;

                  var serializedCandidate = SDPUtils.writeCandidate(cand);
                  event.candidate = Object.assign(
                    event.candidate,
                    SDPUtils.parseCandidate(serializedCandidate)
                  );

                  event.candidate.candidate = serializedCandidate;
                  event.candidate.toJSON = function() {
                    return {
                      candidate: event.candidate.candidate,
                      sdpMid: event.candidate.sdpMid,
                      sdpMLineIndex: event.candidate.sdpMLineIndex,
                      usernameFragment: event.candidate.usernameFragment,
                    };
                  };
                }

                // update local description.
                var sections = SDPUtils.getMediaSections(
                  pc._localDescription.sdp
                );
                if (!end) {
                  sections[event.candidate.sdpMLineIndex] +=
                    'a=' + event.candidate.candidate + '\r\n';
                } else {
                  sections[event.candidate.sdpMLineIndex] +=
                    'a=end-of-candidates\r\n';
                }
                pc._localDescription.sdp =
                  SDPUtils.getDescription(pc._localDescription.sdp) +
                  sections.join('');
                var complete = pc.transceivers.every(function(transceiver) {
                  return (
                    transceiver.iceGatherer &&
                    transceiver.iceGatherer.state === 'completed'
                  );
                });

                if (pc.iceGatheringState !== 'gathering') {
                  pc.iceGatheringState = 'gathering';
                  pc._emitGatheringStateChange();
                }

                // Emit candidate. Also emit null candidate when all gatherers are
                // complete.
                if (!end) {
                  pc._dispatchEvent('icecandidate', event);
                }
                if (complete) {
                  pc._dispatchEvent('icecandidate', new Event('icecandidate'));
                  pc.iceGatheringState = 'complete';
                  pc._emitGatheringStateChange();
                }
              };

              // emit already gathered candidates.
              window.setTimeout(function() {
                bufferedCandidateEvents.forEach(function(e) {
                  iceGatherer.onlocalcandidate(e);
                });
              }, 0);
            };

            // Create ICE transport and DTLS transport.
            RTCPeerConnection.prototype._createIceAndDtlsTransports = function() {
              var pc = this;
              var iceTransport = new window.RTCIceTransport(null);
              iceTransport.onicestatechange = function() {
                pc._updateIceConnectionState();
                pc._updateConnectionState();
              };

              var dtlsTransport = new window.RTCDtlsTransport(iceTransport);
              dtlsTransport.ondtlsstatechange = function() {
                pc._updateConnectionState();
              };
              dtlsTransport.onerror = function() {
                // onerror does not set state to failed by itself.
                Object.defineProperty(dtlsTransport, 'state', {
                  value: 'failed',
                  writable: true,
                });
                pc._updateConnectionState();
              };

              return {
                iceTransport: iceTransport,
                dtlsTransport: dtlsTransport,
              };
            };

            // Destroy ICE gatherer, ICE transport and DTLS transport.
            // Without triggering the callbacks.
            RTCPeerConnection.prototype._disposeIceAndDtlsTransports = function(
              sdpMLineIndex
            ) {
              var iceGatherer = this.transceivers[sdpMLineIndex].iceGatherer;
              if (iceGatherer) {
                delete iceGatherer.onlocalcandidate;
                delete this.transceivers[sdpMLineIndex].iceGatherer;
              }
              var iceTransport = this.transceivers[sdpMLineIndex].iceTransport;
              if (iceTransport) {
                delete iceTransport.onicestatechange;
                delete this.transceivers[sdpMLineIndex].iceTransport;
              }
              var dtlsTransport = this.transceivers[sdpMLineIndex]
                .dtlsTransport;
              if (dtlsTransport) {
                delete dtlsTransport.ondtlsstatechange;
                delete dtlsTransport.onerror;
                delete this.transceivers[sdpMLineIndex].dtlsTransport;
              }
            };

            // Start the RTP Sender and Receiver for a transceiver.
            RTCPeerConnection.prototype._transceive = function(
              transceiver,
              send,
              recv
            ) {
              var params = getCommonCapabilities(
                transceiver.localCapabilities,
                transceiver.remoteCapabilities
              );
              if (send && transceiver.rtpSender) {
                params.encodings = transceiver.sendEncodingParameters;
                params.rtcp = {
                  cname: SDPUtils.localCName,
                  compound: transceiver.rtcpParameters.compound,
                };
                if (transceiver.recvEncodingParameters.length) {
                  params.rtcp.ssrc = transceiver.recvEncodingParameters[0].ssrc;
                }
                transceiver.rtpSender.send(params);
              }
              if (recv && transceiver.rtpReceiver && params.codecs.length > 0) {
                // remove RTX field in Edge 14942
                if (
                  transceiver.kind === 'video' &&
                  transceiver.recvEncodingParameters &&
                  edgeVersion < 15019
                ) {
                  transceiver.recvEncodingParameters.forEach(function(p) {
                    delete p.rtx;
                  });
                }
                if (transceiver.recvEncodingParameters.length) {
                  params.encodings = transceiver.recvEncodingParameters;
                } else {
                  params.encodings = [{}];
                }
                params.rtcp = {
                  compound: transceiver.rtcpParameters.compound,
                };
                if (transceiver.rtcpParameters.cname) {
                  params.rtcp.cname = transceiver.rtcpParameters.cname;
                }
                if (transceiver.sendEncodingParameters.length) {
                  params.rtcp.ssrc = transceiver.sendEncodingParameters[0].ssrc;
                }
                transceiver.rtpReceiver.receive(params);
              }
            };

            RTCPeerConnection.prototype.setLocalDescription = function(
              description
            ) {
              var pc = this;

              // Note: pranswer is not supported.
              if (['offer', 'answer'].indexOf(description.type) === -1) {
                return Promise.reject(
                  makeError(
                    'TypeError',
                    'Unsupported type "' + description.type + '"'
                  )
                );
              }

              if (
                !isActionAllowedInSignalingState(
                  'setLocalDescription',
                  description.type,
                  pc.signalingState
                ) ||
                pc._isClosed
              ) {
                return Promise.reject(
                  makeError(
                    'InvalidStateError',
                    'Can not set local ' +
                      description.type +
                      ' in state ' +
                      pc.signalingState
                  )
                );
              }

              var sections;
              var sessionpart;
              if (description.type === 'offer') {
                // VERY limited support for SDP munging. Limited to:
                // * changing the order of codecs
                sections = SDPUtils.splitSections(description.sdp);
                sessionpart = sections.shift();
                sections.forEach(function(mediaSection, sdpMLineIndex) {
                  var caps = SDPUtils.parseRtpParameters(mediaSection);
                  pc.transceivers[sdpMLineIndex].localCapabilities = caps;
                });

                pc.transceivers.forEach(function(transceiver, sdpMLineIndex) {
                  pc._gather(transceiver.mid, sdpMLineIndex);
                });
              } else if (description.type === 'answer') {
                sections = SDPUtils.splitSections(pc._remoteDescription.sdp);
                sessionpart = sections.shift();
                var isIceLite =
                  SDPUtils.matchPrefix(sessionpart, 'a=ice-lite').length > 0;
                sections.forEach(function(mediaSection, sdpMLineIndex) {
                  var transceiver = pc.transceivers[sdpMLineIndex];
                  var iceGatherer = transceiver.iceGatherer;
                  var iceTransport = transceiver.iceTransport;
                  var dtlsTransport = transceiver.dtlsTransport;
                  var localCapabilities = transceiver.localCapabilities;
                  var remoteCapabilities = transceiver.remoteCapabilities;

                  // treat bundle-only as not-rejected.
                  var rejected =
                    SDPUtils.isRejected(mediaSection) &&
                    SDPUtils.matchPrefix(mediaSection, 'a=bundle-only')
                      .length === 0;

                  if (!rejected && !transceiver.rejected) {
                    var remoteIceParameters = SDPUtils.getIceParameters(
                      mediaSection,
                      sessionpart
                    );
                    var remoteDtlsParameters = SDPUtils.getDtlsParameters(
                      mediaSection,
                      sessionpart
                    );
                    if (isIceLite) {
                      remoteDtlsParameters.role = 'server';
                    }

                    if (!pc.usingBundle || sdpMLineIndex === 0) {
                      pc._gather(transceiver.mid, sdpMLineIndex);
                      if (iceTransport.state === 'new') {
                        iceTransport.start(
                          iceGatherer,
                          remoteIceParameters,
                          isIceLite ? 'controlling' : 'controlled'
                        );
                      }
                      if (dtlsTransport.state === 'new') {
                        dtlsTransport.start(remoteDtlsParameters);
                      }
                    }

                    // Calculate intersection of capabilities.
                    var params = getCommonCapabilities(
                      localCapabilities,
                      remoteCapabilities
                    );

                    // Start the RTCRtpSender. The RTCRtpReceiver for this
                    // transceiver has already been started in setRemoteDescription.
                    pc._transceive(
                      transceiver,
                      params.codecs.length > 0,
                      false
                    );
                  }
                });
              }

              pc._localDescription = {
                type: description.type,
                sdp: description.sdp,
              };
              if (description.type === 'offer') {
                pc._updateSignalingState('have-local-offer');
              } else {
                pc._updateSignalingState('stable');
              }

              return Promise.resolve();
            };

            RTCPeerConnection.prototype.setRemoteDescription = function(
              description
            ) {
              var pc = this;

              // Note: pranswer is not supported.
              if (['offer', 'answer'].indexOf(description.type) === -1) {
                return Promise.reject(
                  makeError(
                    'TypeError',
                    'Unsupported type "' + description.type + '"'
                  )
                );
              }

              if (
                !isActionAllowedInSignalingState(
                  'setRemoteDescription',
                  description.type,
                  pc.signalingState
                ) ||
                pc._isClosed
              ) {
                return Promise.reject(
                  makeError(
                    'InvalidStateError',
                    'Can not set remote ' +
                      description.type +
                      ' in state ' +
                      pc.signalingState
                  )
                );
              }

              var streams = {};
              pc.remoteStreams.forEach(function(stream) {
                streams[stream.id] = stream;
              });
              var receiverList = [];
              var sections = SDPUtils.splitSections(description.sdp);
              var sessionpart = sections.shift();
              var isIceLite =
                SDPUtils.matchPrefix(sessionpart, 'a=ice-lite').length > 0;
              var usingBundle =
                SDPUtils.matchPrefix(sessionpart, 'a=group:BUNDLE ').length > 0;
              pc.usingBundle = usingBundle;
              var iceOptions = SDPUtils.matchPrefix(
                sessionpart,
                'a=ice-options:'
              )[0];
              if (iceOptions) {
                pc.canTrickleIceCandidates =
                  iceOptions
                    .substr(14)
                    .split(' ')
                    .indexOf('trickle') >= 0;
              } else {
                pc.canTrickleIceCandidates = false;
              }

              sections.forEach(function(mediaSection, sdpMLineIndex) {
                var lines = SDPUtils.splitLines(mediaSection);
                var kind = SDPUtils.getKind(mediaSection);
                // treat bundle-only as not-rejected.
                var rejected =
                  SDPUtils.isRejected(mediaSection) &&
                  SDPUtils.matchPrefix(mediaSection, 'a=bundle-only').length ===
                    0;
                var protocol = lines[0].substr(2).split(' ')[2];

                var direction = SDPUtils.getDirection(
                  mediaSection,
                  sessionpart
                );
                var remoteMsid = SDPUtils.parseMsid(mediaSection);

                var mid =
                  SDPUtils.getMid(mediaSection) ||
                  SDPUtils.generateIdentifier();

                // Reject datachannels which are not implemented yet.
                if (
                  rejected ||
                  (kind === 'application' &&
                    (protocol === 'DTLS/SCTP' || protocol === 'UDP/DTLS/SCTP'))
                ) {
                  // TODO: this is dangerous in the case where a non-rejected m-line
                  //     becomes rejected.
                  pc.transceivers[sdpMLineIndex] = {
                    mid: mid,
                    kind: kind,
                    protocol: protocol,
                    rejected: true,
                  };
                  return;
                }

                if (
                  !rejected &&
                  pc.transceivers[sdpMLineIndex] &&
                  pc.transceivers[sdpMLineIndex].rejected
                ) {
                  // recycle a rejected transceiver.
                  pc.transceivers[sdpMLineIndex] = pc._createTransceiver(
                    kind,
                    true
                  );
                }

                var transceiver;
                var iceGatherer;
                var iceTransport;
                var dtlsTransport;
                var rtpReceiver;
                var sendEncodingParameters;
                var recvEncodingParameters;
                var localCapabilities;

                var track;
                // FIXME: ensure the mediaSection has rtcp-mux set.
                var remoteCapabilities = SDPUtils.parseRtpParameters(
                  mediaSection
                );
                var remoteIceParameters;
                var remoteDtlsParameters;
                if (!rejected) {
                  remoteIceParameters = SDPUtils.getIceParameters(
                    mediaSection,
                    sessionpart
                  );
                  remoteDtlsParameters = SDPUtils.getDtlsParameters(
                    mediaSection,
                    sessionpart
                  );
                  remoteDtlsParameters.role = 'client';
                }
                recvEncodingParameters = SDPUtils.parseRtpEncodingParameters(
                  mediaSection
                );

                var rtcpParameters = SDPUtils.parseRtcpParameters(mediaSection);

                var isComplete =
                  SDPUtils.matchPrefix(
                    mediaSection,
                    'a=end-of-candidates',
                    sessionpart
                  ).length > 0;
                var cands = SDPUtils.matchPrefix(mediaSection, 'a=candidate:')
                  .map(function(cand) {
                    return SDPUtils.parseCandidate(cand);
                  })
                  .filter(function(cand) {
                    return cand.component === 1;
                  });

                // Check if we can use BUNDLE and dispose transports.
                if (
                  (description.type === 'offer' ||
                    description.type === 'answer') &&
                  !rejected &&
                  usingBundle &&
                  sdpMLineIndex > 0 &&
                  pc.transceivers[sdpMLineIndex]
                ) {
                  pc._disposeIceAndDtlsTransports(sdpMLineIndex);
                  pc.transceivers[sdpMLineIndex].iceGatherer =
                    pc.transceivers[0].iceGatherer;
                  pc.transceivers[sdpMLineIndex].iceTransport =
                    pc.transceivers[0].iceTransport;
                  pc.transceivers[sdpMLineIndex].dtlsTransport =
                    pc.transceivers[0].dtlsTransport;
                  if (pc.transceivers[sdpMLineIndex].rtpSender) {
                    pc.transceivers[sdpMLineIndex].rtpSender.setTransport(
                      pc.transceivers[0].dtlsTransport
                    );
                  }
                  if (pc.transceivers[sdpMLineIndex].rtpReceiver) {
                    pc.transceivers[sdpMLineIndex].rtpReceiver.setTransport(
                      pc.transceivers[0].dtlsTransport
                    );
                  }
                }
                if (description.type === 'offer' && !rejected) {
                  transceiver =
                    pc.transceivers[sdpMLineIndex] ||
                    pc._createTransceiver(kind);
                  transceiver.mid = mid;

                  if (!transceiver.iceGatherer) {
                    transceiver.iceGatherer = pc._createIceGatherer(
                      sdpMLineIndex,
                      usingBundle
                    );
                  }

                  if (
                    cands.length &&
                    transceiver.iceTransport.state === 'new'
                  ) {
                    if (isComplete && (!usingBundle || sdpMLineIndex === 0)) {
                      transceiver.iceTransport.setRemoteCandidates(cands);
                    } else {
                      cands.forEach(function(candidate) {
                        maybeAddCandidate(transceiver.iceTransport, candidate);
                      });
                    }
                  }

                  localCapabilities = window.RTCRtpReceiver.getCapabilities(
                    kind
                  );

                  // filter RTX until additional stuff needed for RTX is implemented
                  // in adapter.js
                  if (edgeVersion < 15019) {
                    localCapabilities.codecs = localCapabilities.codecs.filter(
                      function(codec) {
                        return codec.name !== 'rtx';
                      }
                    );
                  }

                  sendEncodingParameters = transceiver.sendEncodingParameters || [
                    {
                      ssrc: (2 * sdpMLineIndex + 2) * 1001,
                    },
                  ];

                  // TODO: rewrite to use http://w3c.github.io/webrtc-pc/#set-associated-remote-streams
                  var isNewTrack = false;
                  if (direction === 'sendrecv' || direction === 'sendonly') {
                    isNewTrack = !transceiver.rtpReceiver;
                    rtpReceiver =
                      transceiver.rtpReceiver ||
                      new window.RTCRtpReceiver(
                        transceiver.dtlsTransport,
                        kind
                      );

                    if (isNewTrack) {
                      var stream;
                      track = rtpReceiver.track;
                      // FIXME: does not work with Plan B.
                      if (remoteMsid && remoteMsid.stream === '-') {
                        // no-op. a stream id of '-' means: no associated stream.
                      } else if (remoteMsid) {
                        if (!streams[remoteMsid.stream]) {
                          streams[remoteMsid.stream] = new window.MediaStream();
                          Object.defineProperty(
                            streams[remoteMsid.stream],
                            'id',
                            {
                              get: function() {
                                return remoteMsid.stream;
                              },
                            }
                          );
                        }
                        Object.defineProperty(track, 'id', {
                          get: function() {
                            return remoteMsid.track;
                          },
                        });
                        stream = streams[remoteMsid.stream];
                      } else {
                        if (!streams.default) {
                          streams.default = new window.MediaStream();
                        }
                        stream = streams.default;
                      }
                      if (stream) {
                        addTrackToStreamAndFireEvent(track, stream);
                        transceiver.associatedRemoteMediaStreams.push(stream);
                      }
                      receiverList.push([track, rtpReceiver, stream]);
                    }
                  } else if (
                    transceiver.rtpReceiver &&
                    transceiver.rtpReceiver.track
                  ) {
                    transceiver.associatedRemoteMediaStreams.forEach(function(
                      s
                    ) {
                      var nativeTrack = s.getTracks().find(function(t) {
                        return t.id === transceiver.rtpReceiver.track.id;
                      });
                      if (nativeTrack) {
                        removeTrackFromStreamAndFireEvent(nativeTrack, s);
                      }
                    });
                    transceiver.associatedRemoteMediaStreams = [];
                  }

                  transceiver.localCapabilities = localCapabilities;
                  transceiver.remoteCapabilities = remoteCapabilities;
                  transceiver.rtpReceiver = rtpReceiver;
                  transceiver.rtcpParameters = rtcpParameters;
                  transceiver.sendEncodingParameters = sendEncodingParameters;
                  transceiver.recvEncodingParameters = recvEncodingParameters;

                  // Start the RTCRtpReceiver now. The RTPSender is started in
                  // setLocalDescription.
                  pc._transceive(
                    pc.transceivers[sdpMLineIndex],
                    false,
                    isNewTrack
                  );
                } else if (description.type === 'answer' && !rejected) {
                  transceiver = pc.transceivers[sdpMLineIndex];
                  iceGatherer = transceiver.iceGatherer;
                  iceTransport = transceiver.iceTransport;
                  dtlsTransport = transceiver.dtlsTransport;
                  rtpReceiver = transceiver.rtpReceiver;
                  sendEncodingParameters = transceiver.sendEncodingParameters;
                  localCapabilities = transceiver.localCapabilities;

                  pc.transceivers[
                    sdpMLineIndex
                  ].recvEncodingParameters = recvEncodingParameters;
                  pc.transceivers[
                    sdpMLineIndex
                  ].remoteCapabilities = remoteCapabilities;
                  pc.transceivers[
                    sdpMLineIndex
                  ].rtcpParameters = rtcpParameters;

                  if (cands.length && iceTransport.state === 'new') {
                    if (
                      (isIceLite || isComplete) &&
                      (!usingBundle || sdpMLineIndex === 0)
                    ) {
                      iceTransport.setRemoteCandidates(cands);
                    } else {
                      cands.forEach(function(candidate) {
                        maybeAddCandidate(transceiver.iceTransport, candidate);
                      });
                    }
                  }

                  if (!usingBundle || sdpMLineIndex === 0) {
                    if (iceTransport.state === 'new') {
                      iceTransport.start(
                        iceGatherer,
                        remoteIceParameters,
                        'controlling'
                      );
                    }
                    if (dtlsTransport.state === 'new') {
                      dtlsTransport.start(remoteDtlsParameters);
                    }
                  }

                  // If the offer contained RTX but the answer did not,
                  // remove RTX from sendEncodingParameters.
                  var commonCapabilities = getCommonCapabilities(
                    transceiver.localCapabilities,
                    transceiver.remoteCapabilities
                  );

                  var hasRtx = commonCapabilities.codecs.filter(function(c) {
                    return c.name.toLowerCase() === 'rtx';
                  }).length;
                  if (!hasRtx && transceiver.sendEncodingParameters[0].rtx) {
                    delete transceiver.sendEncodingParameters[0].rtx;
                  }

                  pc._transceive(
                    transceiver,
                    direction === 'sendrecv' || direction === 'recvonly',
                    direction === 'sendrecv' || direction === 'sendonly'
                  );

                  // TODO: rewrite to use http://w3c.github.io/webrtc-pc/#set-associated-remote-streams
                  if (
                    rtpReceiver &&
                    (direction === 'sendrecv' || direction === 'sendonly')
                  ) {
                    track = rtpReceiver.track;
                    if (remoteMsid) {
                      if (!streams[remoteMsid.stream]) {
                        streams[remoteMsid.stream] = new window.MediaStream();
                      }
                      addTrackToStreamAndFireEvent(
                        track,
                        streams[remoteMsid.stream]
                      );
                      receiverList.push([
                        track,
                        rtpReceiver,
                        streams[remoteMsid.stream],
                      ]);
                    } else {
                      if (!streams.default) {
                        streams.default = new window.MediaStream();
                      }
                      addTrackToStreamAndFireEvent(track, streams.default);
                      receiverList.push([track, rtpReceiver, streams.default]);
                    }
                  } else {
                    // FIXME: actually the receiver should be created later.
                    delete transceiver.rtpReceiver;
                  }
                }
              });

              if (pc._dtlsRole === undefined) {
                pc._dtlsRole =
                  description.type === 'offer' ? 'active' : 'passive';
              }

              pc._remoteDescription = {
                type: description.type,
                sdp: description.sdp,
              };
              if (description.type === 'offer') {
                pc._updateSignalingState('have-remote-offer');
              } else {
                pc._updateSignalingState('stable');
              }
              Object.keys(streams).forEach(function(sid) {
                var stream = streams[sid];
                if (stream.getTracks().length) {
                  if (pc.remoteStreams.indexOf(stream) === -1) {
                    pc.remoteStreams.push(stream);
                    var event = new Event('addstream');
                    event.stream = stream;
                    window.setTimeout(function() {
                      pc._dispatchEvent('addstream', event);
                    });
                  }

                  receiverList.forEach(function(item) {
                    var track = item[0];
                    var receiver = item[1];
                    if (stream.id !== item[2].id) {
                      return;
                    }
                    fireAddTrack(pc, track, receiver, [stream]);
                  });
                }
              });
              receiverList.forEach(function(item) {
                if (item[2]) {
                  return;
                }
                fireAddTrack(pc, item[0], item[1], []);
              });

              // check whether addIceCandidate({}) was called within four seconds after
              // setRemoteDescription.
              window.setTimeout(function() {
                if (!(pc && pc.transceivers)) {
                  return;
                }
                pc.transceivers.forEach(function(transceiver) {
                  if (
                    transceiver.iceTransport &&
                    transceiver.iceTransport.state === 'new' &&
                    transceiver.iceTransport.getRemoteCandidates().length > 0
                  ) {
                    console.warn(
                      'Timeout for addRemoteCandidate. Consider sending ' +
                        'an end-of-candidates notification'
                    );
                    transceiver.iceTransport.addRemoteCandidate({});
                  }
                });
              }, 4000);

              return Promise.resolve();
            };

            RTCPeerConnection.prototype.close = function() {
              this.transceivers.forEach(function(transceiver) {
                /* not yet
      if (transceiver.iceGatherer) {
        transceiver.iceGatherer.close();
      }
      */
                if (transceiver.iceTransport) {
                  transceiver.iceTransport.stop();
                }
                if (transceiver.dtlsTransport) {
                  transceiver.dtlsTransport.stop();
                }
                if (transceiver.rtpSender) {
                  transceiver.rtpSender.stop();
                }
                if (transceiver.rtpReceiver) {
                  transceiver.rtpReceiver.stop();
                }
              });
              // FIXME: clean up tracks, local streams, remote streams, etc
              this._isClosed = true;
              this._updateSignalingState('closed');
            };

            // Update the signaling state.
            RTCPeerConnection.prototype._updateSignalingState = function(
              newState
            ) {
              this.signalingState = newState;
              var event = new Event('signalingstatechange');
              this._dispatchEvent('signalingstatechange', event);
            };

            // Determine whether to fire the negotiationneeded event.
            RTCPeerConnection.prototype._maybeFireNegotiationNeeded = function() {
              var pc = this;
              if (
                this.signalingState !== 'stable' ||
                this.needNegotiation === true
              ) {
                return;
              }
              this.needNegotiation = true;
              window.setTimeout(function() {
                if (pc.needNegotiation) {
                  pc.needNegotiation = false;
                  var event = new Event('negotiationneeded');
                  pc._dispatchEvent('negotiationneeded', event);
                }
              }, 0);
            };

            // Update the ice connection state.
            RTCPeerConnection.prototype._updateIceConnectionState = function() {
              var newState;
              var states = {
                new: 0,
                closed: 0,
                checking: 0,
                connected: 0,
                completed: 0,
                disconnected: 0,
                failed: 0,
              };
              this.transceivers.forEach(function(transceiver) {
                if (transceiver.iceTransport && !transceiver.rejected) {
                  states[transceiver.iceTransport.state]++;
                }
              });

              newState = 'new';
              if (states.failed > 0) {
                newState = 'failed';
              } else if (states.checking > 0) {
                newState = 'checking';
              } else if (states.disconnected > 0) {
                newState = 'disconnected';
              } else if (states.new > 0) {
                newState = 'new';
              } else if (states.connected > 0) {
                newState = 'connected';
              } else if (states.completed > 0) {
                newState = 'completed';
              }

              if (newState !== this.iceConnectionState) {
                this.iceConnectionState = newState;
                var event = new Event('iceconnectionstatechange');
                this._dispatchEvent('iceconnectionstatechange', event);
              }
            };

            // Update the connection state.
            RTCPeerConnection.prototype._updateConnectionState = function() {
              var newState;
              var states = {
                new: 0,
                closed: 0,
                connecting: 0,
                connected: 0,
                completed: 0,
                disconnected: 0,
                failed: 0,
              };
              this.transceivers.forEach(function(transceiver) {
                if (
                  transceiver.iceTransport &&
                  transceiver.dtlsTransport &&
                  !transceiver.rejected
                ) {
                  states[transceiver.iceTransport.state]++;
                  states[transceiver.dtlsTransport.state]++;
                }
              });
              // ICETransport.completed and connected are the same for this purpose.
              states.connected += states.completed;

              newState = 'new';
              if (states.failed > 0) {
                newState = 'failed';
              } else if (states.connecting > 0) {
                newState = 'connecting';
              } else if (states.disconnected > 0) {
                newState = 'disconnected';
              } else if (states.new > 0) {
                newState = 'new';
              } else if (states.connected > 0) {
                newState = 'connected';
              }

              if (newState !== this.connectionState) {
                this.connectionState = newState;
                var event = new Event('connectionstatechange');
                this._dispatchEvent('connectionstatechange', event);
              }
            };

            RTCPeerConnection.prototype.createOffer = function() {
              var pc = this;

              if (pc._isClosed) {
                return Promise.reject(
                  makeError(
                    'InvalidStateError',
                    'Can not call createOffer after close'
                  )
                );
              }

              var numAudioTracks = pc.transceivers.filter(function(t) {
                return t.kind === 'audio';
              }).length;
              var numVideoTracks = pc.transceivers.filter(function(t) {
                return t.kind === 'video';
              }).length;

              // Determine number of audio and video tracks we need to send/recv.
              var offerOptions = arguments[0];
              if (offerOptions) {
                // Reject Chrome legacy constraints.
                if (offerOptions.mandatory || offerOptions.optional) {
                  throw new TypeError(
                    'Legacy mandatory/optional constraints not supported.'
                  );
                }
                if (offerOptions.offerToReceiveAudio !== undefined) {
                  if (offerOptions.offerToReceiveAudio === true) {
                    numAudioTracks = 1;
                  } else if (offerOptions.offerToReceiveAudio === false) {
                    numAudioTracks = 0;
                  } else {
                    numAudioTracks = offerOptions.offerToReceiveAudio;
                  }
                }
                if (offerOptions.offerToReceiveVideo !== undefined) {
                  if (offerOptions.offerToReceiveVideo === true) {
                    numVideoTracks = 1;
                  } else if (offerOptions.offerToReceiveVideo === false) {
                    numVideoTracks = 0;
                  } else {
                    numVideoTracks = offerOptions.offerToReceiveVideo;
                  }
                }
              }

              pc.transceivers.forEach(function(transceiver) {
                if (transceiver.kind === 'audio') {
                  numAudioTracks--;
                  if (numAudioTracks < 0) {
                    transceiver.wantReceive = false;
                  }
                } else if (transceiver.kind === 'video') {
                  numVideoTracks--;
                  if (numVideoTracks < 0) {
                    transceiver.wantReceive = false;
                  }
                }
              });

              // Create M-lines for recvonly streams.
              while (numAudioTracks > 0 || numVideoTracks > 0) {
                if (numAudioTracks > 0) {
                  pc._createTransceiver('audio');
                  numAudioTracks--;
                }
                if (numVideoTracks > 0) {
                  pc._createTransceiver('video');
                  numVideoTracks--;
                }
              }

              var sdp = SDPUtils.writeSessionBoilerplate(
                pc._sdpSessionId,
                pc._sdpSessionVersion++
              );
              pc.transceivers.forEach(function(transceiver, sdpMLineIndex) {
                // For each track, create an ice gatherer, ice transport,
                // dtls transport, potentially rtpsender and rtpreceiver.
                var track = transceiver.track;
                var kind = transceiver.kind;
                var mid = transceiver.mid || SDPUtils.generateIdentifier();
                transceiver.mid = mid;

                if (!transceiver.iceGatherer) {
                  transceiver.iceGatherer = pc._createIceGatherer(
                    sdpMLineIndex,
                    pc.usingBundle
                  );
                }

                var localCapabilities = window.RTCRtpSender.getCapabilities(
                  kind
                );
                // filter RTX until additional stuff needed for RTX is implemented
                // in adapter.js
                if (edgeVersion < 15019) {
                  localCapabilities.codecs = localCapabilities.codecs.filter(
                    function(codec) {
                      return codec.name !== 'rtx';
                    }
                  );
                }
                localCapabilities.codecs.forEach(function(codec) {
                  // work around https://bugs.chromium.org/p/webrtc/issues/detail?id=6552
                  // by adding level-asymmetry-allowed=1
                  if (
                    codec.name === 'H264' &&
                    codec.parameters['level-asymmetry-allowed'] === undefined
                  ) {
                    codec.parameters['level-asymmetry-allowed'] = '1';
                  }

                  // for subsequent offers, we might have to re-use the payload
                  // type of the last offer.
                  if (
                    transceiver.remoteCapabilities &&
                    transceiver.remoteCapabilities.codecs
                  ) {
                    transceiver.remoteCapabilities.codecs.forEach(function(
                      remoteCodec
                    ) {
                      if (
                        codec.name.toLowerCase() ===
                          remoteCodec.name.toLowerCase() &&
                        codec.clockRate === remoteCodec.clockRate
                      ) {
                        codec.preferredPayloadType = remoteCodec.payloadType;
                      }
                    });
                  }
                });
                localCapabilities.headerExtensions.forEach(function(hdrExt) {
                  var remoteExtensions =
                    (transceiver.remoteCapabilities &&
                      transceiver.remoteCapabilities.headerExtensions) ||
                    [];
                  remoteExtensions.forEach(function(rHdrExt) {
                    if (hdrExt.uri === rHdrExt.uri) {
                      hdrExt.id = rHdrExt.id;
                    }
                  });
                });

                // generate an ssrc now, to be used later in rtpSender.send
                var sendEncodingParameters = transceiver.sendEncodingParameters || [
                  {
                    ssrc: (2 * sdpMLineIndex + 1) * 1001,
                  },
                ];
                if (track) {
                  // add RTX
                  if (
                    edgeVersion >= 15019 &&
                    kind === 'video' &&
                    !sendEncodingParameters[0].rtx
                  ) {
                    sendEncodingParameters[0].rtx = {
                      ssrc: sendEncodingParameters[0].ssrc + 1,
                    };
                  }
                }

                if (transceiver.wantReceive) {
                  transceiver.rtpReceiver = new window.RTCRtpReceiver(
                    transceiver.dtlsTransport,
                    kind
                  );
                }

                transceiver.localCapabilities = localCapabilities;
                transceiver.sendEncodingParameters = sendEncodingParameters;
              });

              // always offer BUNDLE and dispose on return if not supported.
              if (pc._config.bundlePolicy !== 'max-compat') {
                sdp +=
                  'a=group:BUNDLE ' +
                  pc.transceivers
                    .map(function(t) {
                      return t.mid;
                    })
                    .join(' ') +
                  '\r\n';
              }
              sdp += 'a=ice-options:trickle\r\n';

              pc.transceivers.forEach(function(transceiver, sdpMLineIndex) {
                sdp += writeMediaSection(
                  transceiver,
                  transceiver.localCapabilities,
                  'offer',
                  transceiver.stream,
                  pc._dtlsRole
                );
                sdp += 'a=rtcp-rsize\r\n';

                if (
                  transceiver.iceGatherer &&
                  pc.iceGatheringState !== 'new' &&
                  (sdpMLineIndex === 0 || !pc.usingBundle)
                ) {
                  transceiver.iceGatherer
                    .getLocalCandidates()
                    .forEach(function(cand) {
                      cand.component = 1;
                      sdp += 'a=' + SDPUtils.writeCandidate(cand) + '\r\n';
                    });

                  if (transceiver.iceGatherer.state === 'completed') {
                    sdp += 'a=end-of-candidates\r\n';
                  }
                }
              });

              var desc = new window.RTCSessionDescription({
                type: 'offer',
                sdp: sdp,
              });
              return Promise.resolve(desc);
            };

            RTCPeerConnection.prototype.createAnswer = function() {
              var pc = this;

              if (pc._isClosed) {
                return Promise.reject(
                  makeError(
                    'InvalidStateError',
                    'Can not call createAnswer after close'
                  )
                );
              }

              if (
                !(
                  pc.signalingState === 'have-remote-offer' ||
                  pc.signalingState === 'have-local-pranswer'
                )
              ) {
                return Promise.reject(
                  makeError(
                    'InvalidStateError',
                    'Can not call createAnswer in signalingState ' +
                      pc.signalingState
                  )
                );
              }

              var sdp = SDPUtils.writeSessionBoilerplate(
                pc._sdpSessionId,
                pc._sdpSessionVersion++
              );
              if (pc.usingBundle) {
                sdp +=
                  'a=group:BUNDLE ' +
                  pc.transceivers
                    .map(function(t) {
                      return t.mid;
                    })
                    .join(' ') +
                  '\r\n';
              }
              sdp += 'a=ice-options:trickle\r\n';

              var mediaSectionsInOffer = SDPUtils.getMediaSections(
                pc._remoteDescription.sdp
              ).length;
              pc.transceivers.forEach(function(transceiver, sdpMLineIndex) {
                if (sdpMLineIndex + 1 > mediaSectionsInOffer) {
                  return;
                }
                if (transceiver.rejected) {
                  if (transceiver.kind === 'application') {
                    if (transceiver.protocol === 'DTLS/SCTP') {
                      // legacy fmt
                      sdp += 'm=application 0 DTLS/SCTP 5000\r\n';
                    } else {
                      sdp +=
                        'm=application 0 ' +
                        transceiver.protocol +
                        ' webrtc-datachannel\r\n';
                    }
                  } else if (transceiver.kind === 'audio') {
                    sdp +=
                      'm=audio 0 UDP/TLS/RTP/SAVPF 0\r\n' +
                      'a=rtpmap:0 PCMU/8000\r\n';
                  } else if (transceiver.kind === 'video') {
                    sdp +=
                      'm=video 0 UDP/TLS/RTP/SAVPF 120\r\n' +
                      'a=rtpmap:120 VP8/90000\r\n';
                  }
                  sdp +=
                    'c=IN IP4 0.0.0.0\r\n' +
                    'a=inactive\r\n' +
                    'a=mid:' +
                    transceiver.mid +
                    '\r\n';
                  return;
                }

                // FIXME: look at direction.
                if (transceiver.stream) {
                  var localTrack;
                  if (transceiver.kind === 'audio') {
                    localTrack = transceiver.stream.getAudioTracks()[0];
                  } else if (transceiver.kind === 'video') {
                    localTrack = transceiver.stream.getVideoTracks()[0];
                  }
                  if (localTrack) {
                    // add RTX
                    if (
                      edgeVersion >= 15019 &&
                      transceiver.kind === 'video' &&
                      !transceiver.sendEncodingParameters[0].rtx
                    ) {
                      transceiver.sendEncodingParameters[0].rtx = {
                        ssrc: transceiver.sendEncodingParameters[0].ssrc + 1,
                      };
                    }
                  }
                }

                // Calculate intersection of capabilities.
                var commonCapabilities = getCommonCapabilities(
                  transceiver.localCapabilities,
                  transceiver.remoteCapabilities
                );

                var hasRtx = commonCapabilities.codecs.filter(function(c) {
                  return c.name.toLowerCase() === 'rtx';
                }).length;
                if (!hasRtx && transceiver.sendEncodingParameters[0].rtx) {
                  delete transceiver.sendEncodingParameters[0].rtx;
                }

                sdp += writeMediaSection(
                  transceiver,
                  commonCapabilities,
                  'answer',
                  transceiver.stream,
                  pc._dtlsRole
                );
                if (
                  transceiver.rtcpParameters &&
                  transceiver.rtcpParameters.reducedSize
                ) {
                  sdp += 'a=rtcp-rsize\r\n';
                }
              });

              var desc = new window.RTCSessionDescription({
                type: 'answer',
                sdp: sdp,
              });
              return Promise.resolve(desc);
            };

            RTCPeerConnection.prototype.addIceCandidate = function(candidate) {
              var pc = this;
              var sections;
              if (
                candidate &&
                !(candidate.sdpMLineIndex !== undefined || candidate.sdpMid)
              ) {
                return Promise.reject(
                  new TypeError('sdpMLineIndex or sdpMid required')
                );
              }

              // TODO: needs to go into ops queue.
              return new Promise(function(resolve, reject) {
                if (!pc._remoteDescription) {
                  return reject(
                    makeError(
                      'InvalidStateError',
                      'Can not add ICE candidate without a remote description'
                    )
                  );
                } else if (!candidate || candidate.candidate === '') {
                  for (var j = 0; j < pc.transceivers.length; j++) {
                    if (pc.transceivers[j].rejected) {
                      continue;
                    }
                    pc.transceivers[j].iceTransport.addRemoteCandidate({});
                    sections = SDPUtils.getMediaSections(
                      pc._remoteDescription.sdp
                    );
                    sections[j] += 'a=end-of-candidates\r\n';
                    pc._remoteDescription.sdp =
                      SDPUtils.getDescription(pc._remoteDescription.sdp) +
                      sections.join('');
                    if (pc.usingBundle) {
                      break;
                    }
                  }
                } else {
                  var sdpMLineIndex = candidate.sdpMLineIndex;
                  if (candidate.sdpMid) {
                    for (var i = 0; i < pc.transceivers.length; i++) {
                      if (pc.transceivers[i].mid === candidate.sdpMid) {
                        sdpMLineIndex = i;
                        break;
                      }
                    }
                  }
                  var transceiver = pc.transceivers[sdpMLineIndex];
                  if (transceiver) {
                    if (transceiver.rejected) {
                      return resolve();
                    }
                    var cand =
                      Object.keys(candidate.candidate).length > 0
                        ? SDPUtils.parseCandidate(candidate.candidate)
                        : {};
                    // Ignore Chrome's invalid candidates since Edge does not like them.
                    if (
                      cand.protocol === 'tcp' &&
                      (cand.port === 0 || cand.port === 9)
                    ) {
                      return resolve();
                    }
                    // Ignore RTCP candidates, we assume RTCP-MUX.
                    if (cand.component && cand.component !== 1) {
                      return resolve();
                    }
                    // when using bundle, avoid adding candidates to the wrong
                    // ice transport. And avoid adding candidates added in the SDP.
                    if (
                      sdpMLineIndex === 0 ||
                      (sdpMLineIndex > 0 &&
                        transceiver.iceTransport !==
                          pc.transceivers[0].iceTransport)
                    ) {
                      if (!maybeAddCandidate(transceiver.iceTransport, cand)) {
                        return reject(
                          makeError(
                            'OperationError',
                            'Can not add ICE candidate'
                          )
                        );
                      }
                    }

                    // update the remoteDescription.
                    var candidateString = candidate.candidate.trim();
                    if (candidateString.indexOf('a=') === 0) {
                      candidateString = candidateString.substr(2);
                    }
                    sections = SDPUtils.getMediaSections(
                      pc._remoteDescription.sdp
                    );
                    sections[sdpMLineIndex] +=
                      'a=' +
                      (cand.type ? candidateString : 'end-of-candidates') +
                      '\r\n';
                    pc._remoteDescription.sdp =
                      SDPUtils.getDescription(pc._remoteDescription.sdp) +
                      sections.join('');
                  } else {
                    return reject(
                      makeError('OperationError', 'Can not add ICE candidate')
                    );
                  }
                }
                resolve();
              });
            };

            RTCPeerConnection.prototype.getStats = function(selector) {
              if (selector && selector instanceof window.MediaStreamTrack) {
                var senderOrReceiver = null;
                this.transceivers.forEach(function(transceiver) {
                  if (
                    transceiver.rtpSender &&
                    transceiver.rtpSender.track === selector
                  ) {
                    senderOrReceiver = transceiver.rtpSender;
                  } else if (
                    transceiver.rtpReceiver &&
                    transceiver.rtpReceiver.track === selector
                  ) {
                    senderOrReceiver = transceiver.rtpReceiver;
                  }
                });
                if (!senderOrReceiver) {
                  throw makeError('InvalidAccessError', 'Invalid selector.');
                }
                return senderOrReceiver.getStats();
              }

              var promises = [];
              this.transceivers.forEach(function(transceiver) {
                [
                  'rtpSender',
                  'rtpReceiver',
                  'iceGatherer',
                  'iceTransport',
                  'dtlsTransport',
                ].forEach(function(method) {
                  if (transceiver[method]) {
                    promises.push(transceiver[method].getStats());
                  }
                });
              });
              return Promise.all(promises).then(function(allStats) {
                var results = new Map();
                allStats.forEach(function(stats) {
                  stats.forEach(function(stat) {
                    results.set(stat.id, stat);
                  });
                });
                return results;
              });
            };

            // fix low-level stat names and return Map instead of object.
            var ortcObjects = [
              'RTCRtpSender',
              'RTCRtpReceiver',
              'RTCIceGatherer',
              'RTCIceTransport',
              'RTCDtlsTransport',
            ];
            ortcObjects.forEach(function(ortcObjectName) {
              var obj = window[ortcObjectName];
              if (obj && obj.prototype && obj.prototype.getStats) {
                var nativeGetstats = obj.prototype.getStats;
                obj.prototype.getStats = function() {
                  return nativeGetstats.apply(this).then(function(nativeStats) {
                    var mapStats = new Map();
                    Object.keys(nativeStats).forEach(function(id) {
                      nativeStats[id].type = fixStatsType(nativeStats[id]);
                      mapStats.set(id, nativeStats[id]);
                    });
                    return mapStats;
                  });
                };
              }
            });

            // legacy callback shims. Should be moved to adapter.js some days.
            var methods = ['createOffer', 'createAnswer'];
            methods.forEach(function(method) {
              var nativeMethod = RTCPeerConnection.prototype[method];
              RTCPeerConnection.prototype[method] = function() {
                var args = arguments;
                if (
                  typeof args[0] === 'function' ||
                  typeof args[1] === 'function'
                ) {
                  // legacy
                  return nativeMethod.apply(this, [arguments[2]]).then(
                    function(description) {
                      if (typeof args[0] === 'function') {
                        args[0].apply(null, [description]);
                      }
                    },
                    function(error) {
                      if (typeof args[1] === 'function') {
                        args[1].apply(null, [error]);
                      }
                    }
                  );
                }
                return nativeMethod.apply(this, arguments);
              };
            });

            methods = [
              'setLocalDescription',
              'setRemoteDescription',
              'addIceCandidate',
            ];
            methods.forEach(function(method) {
              var nativeMethod = RTCPeerConnection.prototype[method];
              RTCPeerConnection.prototype[method] = function() {
                var args = arguments;
                if (
                  typeof args[1] === 'function' ||
                  typeof args[2] === 'function'
                ) {
                  // legacy
                  return nativeMethod.apply(this, arguments).then(
                    function() {
                      if (typeof args[1] === 'function') {
                        args[1].apply(null);
                      }
                    },
                    function(error) {
                      if (typeof args[2] === 'function') {
                        args[2].apply(null, [error]);
                      }
                    }
                  );
                }
                return nativeMethod.apply(this, arguments);
              };
            });

            // getStats is special. It doesn't have a spec legacy method yet we support
            // getStats(something, cb) without error callbacks.
            ['getStats'].forEach(function(method) {
              var nativeMethod = RTCPeerConnection.prototype[method];
              RTCPeerConnection.prototype[method] = function() {
                var args = arguments;
                if (typeof args[1] === 'function') {
                  return nativeMethod.apply(this, arguments).then(function() {
                    if (typeof args[1] === 'function') {
                      args[1].apply(null);
                    }
                  });
                }
                return nativeMethod.apply(this, arguments);
              };
            });

            return RTCPeerConnection;
          };
        },
        { sdp: 17 },
      ],
      17: [
        function(require, module, exports) {
          /* eslint-env node */
          'use strict';

          // SDP helpers.
          var SDPUtils = {};

          // Generate an alphanumeric identifier for cname or mids.
          // TODO: use UUIDs instead? https://gist.github.com/jed/982883
          SDPUtils.generateIdentifier = function() {
            return Math.random()
              .toString(36)
              .substr(2, 10);
          };

          // The RTCP CNAME used by all peerconnections from the same JS.
          SDPUtils.localCName = SDPUtils.generateIdentifier();

          // Splits SDP into lines, dealing with both CRLF and LF.
          SDPUtils.splitLines = function(blob) {
            return blob
              .trim()
              .split('\n')
              .map(function(line) {
                return line.trim();
              });
          };
          // Splits SDP into sessionpart and mediasections. Ensures CRLF.
          SDPUtils.splitSections = function(blob) {
            var parts = blob.split('\nm=');
            return parts.map(function(part, index) {
              return (index > 0 ? 'm=' + part : part).trim() + '\r\n';
            });
          };

          // returns the session description.
          SDPUtils.getDescription = function(blob) {
            var sections = SDPUtils.splitSections(blob);
            return sections && sections[0];
          };

          // returns the individual media sections.
          SDPUtils.getMediaSections = function(blob) {
            var sections = SDPUtils.splitSections(blob);
            sections.shift();
            return sections;
          };

          // Returns lines that start with a certain prefix.
          SDPUtils.matchPrefix = function(blob, prefix) {
            return SDPUtils.splitLines(blob).filter(function(line) {
              return line.indexOf(prefix) === 0;
            });
          };

          // Parses an ICE candidate line. Sample input:
          // candidate:702786350 2 udp 41819902 8.8.8.8 60769 typ relay raddr 8.8.8.8
          // rport 55996"
          SDPUtils.parseCandidate = function(line) {
            var parts;
            // Parse both variants.
            if (line.indexOf('a=candidate:') === 0) {
              parts = line.substring(12).split(' ');
            } else {
              parts = line.substring(10).split(' ');
            }

            var candidate = {
              foundation: parts[0],
              component: parseInt(parts[1], 10),
              protocol: parts[2].toLowerCase(),
              priority: parseInt(parts[3], 10),
              ip: parts[4],
              address: parts[4], // address is an alias for ip.
              port: parseInt(parts[5], 10),
              // skip parts[6] == 'typ'
              type: parts[7],
            };

            for (var i = 8; i < parts.length; i += 2) {
              switch (parts[i]) {
                case 'raddr':
                  candidate.relatedAddress = parts[i + 1];
                  break;
                case 'rport':
                  candidate.relatedPort = parseInt(parts[i + 1], 10);
                  break;
                case 'tcptype':
                  candidate.tcpType = parts[i + 1];
                  break;
                case 'ufrag':
                  candidate.ufrag = parts[i + 1]; // for backward compability.
                  candidate.usernameFragment = parts[i + 1];
                  break;
                default:
                  // extension handling, in particular ufrag
                  candidate[parts[i]] = parts[i + 1];
                  break;
              }
            }
            return candidate;
          };

          // Translates a candidate object into SDP candidate attribute.
          SDPUtils.writeCandidate = function(candidate) {
            var sdp = [];
            sdp.push(candidate.foundation);
            sdp.push(candidate.component);
            sdp.push(candidate.protocol.toUpperCase());
            sdp.push(candidate.priority);
            sdp.push(candidate.address || candidate.ip);
            sdp.push(candidate.port);

            var type = candidate.type;
            sdp.push('typ');
            sdp.push(type);
            if (
              type !== 'host' &&
              candidate.relatedAddress &&
              candidate.relatedPort
            ) {
              sdp.push('raddr');
              sdp.push(candidate.relatedAddress);
              sdp.push('rport');
              sdp.push(candidate.relatedPort);
            }
            if (
              candidate.tcpType &&
              candidate.protocol.toLowerCase() === 'tcp'
            ) {
              sdp.push('tcptype');
              sdp.push(candidate.tcpType);
            }
            if (candidate.usernameFragment || candidate.ufrag) {
              sdp.push('ufrag');
              sdp.push(candidate.usernameFragment || candidate.ufrag);
            }
            return 'candidate:' + sdp.join(' ');
          };

          // Parses an ice-options line, returns an array of option tags.
          // a=ice-options:foo bar
          SDPUtils.parseIceOptions = function(line) {
            return line.substr(14).split(' ');
          };

          // Parses an rtpmap line, returns RTCRtpCoddecParameters. Sample input:
          // a=rtpmap:111 opus/48000/2
          SDPUtils.parseRtpMap = function(line) {
            var parts = line.substr(9).split(' ');
            var parsed = {
              payloadType: parseInt(parts.shift(), 10), // was: id
            };

            parts = parts[0].split('/');

            parsed.name = parts[0];
            parsed.clockRate = parseInt(parts[1], 10); // was: clockrate
            parsed.channels = parts.length === 3 ? parseInt(parts[2], 10) : 1;
            // legacy alias, got renamed back to channels in ORTC.
            parsed.numChannels = parsed.channels;
            return parsed;
          };

          // Generate an a=rtpmap line from RTCRtpCodecCapability or
          // RTCRtpCodecParameters.
          SDPUtils.writeRtpMap = function(codec) {
            var pt = codec.payloadType;
            if (codec.preferredPayloadType !== undefined) {
              pt = codec.preferredPayloadType;
            }
            var channels = codec.channels || codec.numChannels || 1;
            return (
              'a=rtpmap:' +
              pt +
              ' ' +
              codec.name +
              '/' +
              codec.clockRate +
              (channels !== 1 ? '/' + channels : '') +
              '\r\n'
            );
          };

          // Parses an a=extmap line (headerextension from RFC 5285). Sample input:
          // a=extmap:2 urn:ietf:params:rtp-hdrext:toffset
          // a=extmap:2/sendonly urn:ietf:params:rtp-hdrext:toffset
          SDPUtils.parseExtmap = function(line) {
            var parts = line.substr(9).split(' ');
            return {
              id: parseInt(parts[0], 10),
              direction:
                parts[0].indexOf('/') > 0 ? parts[0].split('/')[1] : 'sendrecv',
              uri: parts[1],
            };
          };

          // Generates a=extmap line from RTCRtpHeaderExtensionParameters or
          // RTCRtpHeaderExtension.
          SDPUtils.writeExtmap = function(headerExtension) {
            return (
              'a=extmap:' +
              (headerExtension.id || headerExtension.preferredId) +
              (headerExtension.direction &&
              headerExtension.direction !== 'sendrecv'
                ? '/' + headerExtension.direction
                : '') +
              ' ' +
              headerExtension.uri +
              '\r\n'
            );
          };

          // Parses an ftmp line, returns dictionary. Sample input:
          // a=fmtp:96 vbr=on;cng=on
          // Also deals with vbr=on; cng=on
          SDPUtils.parseFmtp = function(line) {
            var parsed = {};
            var kv;
            var parts = line.substr(line.indexOf(' ') + 1).split(';');
            for (var j = 0; j < parts.length; j++) {
              kv = parts[j].trim().split('=');
              parsed[kv[0].trim()] = kv[1];
            }
            return parsed;
          };

          // Generates an a=ftmp line from RTCRtpCodecCapability or RTCRtpCodecParameters.
          SDPUtils.writeFmtp = function(codec) {
            var line = '';
            var pt = codec.payloadType;
            if (codec.preferredPayloadType !== undefined) {
              pt = codec.preferredPayloadType;
            }
            if (codec.parameters && Object.keys(codec.parameters).length) {
              var params = [];
              Object.keys(codec.parameters).forEach(function(param) {
                if (codec.parameters[param]) {
                  params.push(param + '=' + codec.parameters[param]);
                } else {
                  params.push(param);
                }
              });
              line += 'a=fmtp:' + pt + ' ' + params.join(';') + '\r\n';
            }
            return line;
          };

          // Parses an rtcp-fb line, returns RTCPRtcpFeedback object. Sample input:
          // a=rtcp-fb:98 nack rpsi
          SDPUtils.parseRtcpFb = function(line) {
            var parts = line.substr(line.indexOf(' ') + 1).split(' ');
            return {
              type: parts.shift(),
              parameter: parts.join(' '),
            };
          };
          // Generate a=rtcp-fb lines from RTCRtpCodecCapability or RTCRtpCodecParameters.
          SDPUtils.writeRtcpFb = function(codec) {
            var lines = '';
            var pt = codec.payloadType;
            if (codec.preferredPayloadType !== undefined) {
              pt = codec.preferredPayloadType;
            }
            if (codec.rtcpFeedback && codec.rtcpFeedback.length) {
              // FIXME: special handling for trr-int?
              codec.rtcpFeedback.forEach(function(fb) {
                lines +=
                  'a=rtcp-fb:' +
                  pt +
                  ' ' +
                  fb.type +
                  (fb.parameter && fb.parameter.length
                    ? ' ' + fb.parameter
                    : '') +
                  '\r\n';
              });
            }
            return lines;
          };

          // Parses an RFC 5576 ssrc media attribute. Sample input:
          // a=ssrc:3735928559 cname:something
          SDPUtils.parseSsrcMedia = function(line) {
            var sp = line.indexOf(' ');
            var parts = {
              ssrc: parseInt(line.substr(7, sp - 7), 10),
            };
            var colon = line.indexOf(':', sp);
            if (colon > -1) {
              parts.attribute = line.substr(sp + 1, colon - sp - 1);
              parts.value = line.substr(colon + 1);
            } else {
              parts.attribute = line.substr(sp + 1);
            }
            return parts;
          };

          SDPUtils.parseSsrcGroup = function(line) {
            var parts = line.substr(13).split(' ');
            return {
              semantics: parts.shift(),
              ssrcs: parts.map(function(ssrc) {
                return parseInt(ssrc, 10);
              }),
            };
          };

          // Extracts the MID (RFC 5888) from a media section.
          // returns the MID or undefined if no mid line was found.
          SDPUtils.getMid = function(mediaSection) {
            var mid = SDPUtils.matchPrefix(mediaSection, 'a=mid:')[0];
            if (mid) {
              return mid.substr(6);
            }
          };

          SDPUtils.parseFingerprint = function(line) {
            var parts = line.substr(14).split(' ');
            return {
              algorithm: parts[0].toLowerCase(), // algorithm is case-sensitive in Edge.
              value: parts[1],
            };
          };

          // Extracts DTLS parameters from SDP media section or sessionpart.
          // FIXME: for consistency with other functions this should only
          //   get the fingerprint line as input. See also getIceParameters.
          SDPUtils.getDtlsParameters = function(mediaSection, sessionpart) {
            var lines = SDPUtils.matchPrefix(
              mediaSection + sessionpart,
              'a=fingerprint:'
            );
            // Note: a=setup line is ignored since we use the 'auto' role.
            // Note2: 'algorithm' is not case sensitive except in Edge.
            return {
              role: 'auto',
              fingerprints: lines.map(SDPUtils.parseFingerprint),
            };
          };

          // Serializes DTLS parameters to SDP.
          SDPUtils.writeDtlsParameters = function(params, setupType) {
            var sdp = 'a=setup:' + setupType + '\r\n';
            params.fingerprints.forEach(function(fp) {
              sdp += 'a=fingerprint:' + fp.algorithm + ' ' + fp.value + '\r\n';
            });
            return sdp;
          };
          // Parses ICE information from SDP media section or sessionpart.
          // FIXME: for consistency with other functions this should only
          //   get the ice-ufrag and ice-pwd lines as input.
          SDPUtils.getIceParameters = function(mediaSection, sessionpart) {
            var lines = SDPUtils.splitLines(mediaSection);
            // Search in session part, too.
            lines = lines.concat(SDPUtils.splitLines(sessionpart));
            var iceParameters = {
              usernameFragment: lines
                .filter(function(line) {
                  return line.indexOf('a=ice-ufrag:') === 0;
                })[0]
                .substr(12),
              password: lines
                .filter(function(line) {
                  return line.indexOf('a=ice-pwd:') === 0;
                })[0]
                .substr(10),
            };
            return iceParameters;
          };

          // Serializes ICE parameters to SDP.
          SDPUtils.writeIceParameters = function(params) {
            return (
              'a=ice-ufrag:' +
              params.usernameFragment +
              '\r\n' +
              'a=ice-pwd:' +
              params.password +
              '\r\n'
            );
          };

          // Parses the SDP media section and returns RTCRtpParameters.
          SDPUtils.parseRtpParameters = function(mediaSection) {
            var description = {
              codecs: [],
              headerExtensions: [],
              fecMechanisms: [],
              rtcp: [],
            };
            var lines = SDPUtils.splitLines(mediaSection);
            var mline = lines[0].split(' ');
            for (var i = 3; i < mline.length; i++) {
              // find all codecs from mline[3..]
              var pt = mline[i];
              var rtpmapline = SDPUtils.matchPrefix(
                mediaSection,
                'a=rtpmap:' + pt + ' '
              )[0];
              if (rtpmapline) {
                var codec = SDPUtils.parseRtpMap(rtpmapline);
                var fmtps = SDPUtils.matchPrefix(
                  mediaSection,
                  'a=fmtp:' + pt + ' '
                );
                // Only the first a=fmtp:<pt> is considered.
                codec.parameters = fmtps.length
                  ? SDPUtils.parseFmtp(fmtps[0])
                  : {};
                codec.rtcpFeedback = SDPUtils.matchPrefix(
                  mediaSection,
                  'a=rtcp-fb:' + pt + ' '
                ).map(SDPUtils.parseRtcpFb);
                description.codecs.push(codec);
                // parse FEC mechanisms from rtpmap lines.
                switch (codec.name.toUpperCase()) {
                  case 'RED':
                  case 'ULPFEC':
                    description.fecMechanisms.push(codec.name.toUpperCase());
                    break;
                  default:
                    // only RED and ULPFEC are recognized as FEC mechanisms.
                    break;
                }
              }
            }
            SDPUtils.matchPrefix(mediaSection, 'a=extmap:').forEach(function(
              line
            ) {
              description.headerExtensions.push(SDPUtils.parseExtmap(line));
            });
            // FIXME: parse rtcp.
            return description;
          };

          // Generates parts of the SDP media section describing the capabilities /
          // parameters.
          SDPUtils.writeRtpDescription = function(kind, caps) {
            var sdp = '';

            // Build the mline.
            sdp += 'm=' + kind + ' ';
            sdp += caps.codecs.length > 0 ? '9' : '0'; // reject if no codecs.
            sdp += ' UDP/TLS/RTP/SAVPF ';
            sdp +=
              caps.codecs
                .map(function(codec) {
                  if (codec.preferredPayloadType !== undefined) {
                    return codec.preferredPayloadType;
                  }
                  return codec.payloadType;
                })
                .join(' ') + '\r\n';

            sdp += 'c=IN IP4 0.0.0.0\r\n';
            sdp += 'a=rtcp:9 IN IP4 0.0.0.0\r\n';

            // Add a=rtpmap lines for each codec. Also fmtp and rtcp-fb.
            caps.codecs.forEach(function(codec) {
              sdp += SDPUtils.writeRtpMap(codec);
              sdp += SDPUtils.writeFmtp(codec);
              sdp += SDPUtils.writeRtcpFb(codec);
            });
            var maxptime = 0;
            caps.codecs.forEach(function(codec) {
              if (codec.maxptime > maxptime) {
                maxptime = codec.maxptime;
              }
            });
            if (maxptime > 0) {
              sdp += 'a=maxptime:' + maxptime + '\r\n';
            }
            sdp += 'a=rtcp-mux\r\n';

            if (caps.headerExtensions) {
              caps.headerExtensions.forEach(function(extension) {
                sdp += SDPUtils.writeExtmap(extension);
              });
            }
            // FIXME: write fecMechanisms.
            return sdp;
          };

          // Parses the SDP media section and returns an array of
          // RTCRtpEncodingParameters.
          SDPUtils.parseRtpEncodingParameters = function(mediaSection) {
            var encodingParameters = [];
            var description = SDPUtils.parseRtpParameters(mediaSection);
            var hasRed = description.fecMechanisms.indexOf('RED') !== -1;
            var hasUlpfec = description.fecMechanisms.indexOf('ULPFEC') !== -1;

            // filter a=ssrc:... cname:, ignore PlanB-msid
            var ssrcs = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:')
              .map(function(line) {
                return SDPUtils.parseSsrcMedia(line);
              })
              .filter(function(parts) {
                return parts.attribute === 'cname';
              });
            var primarySsrc = ssrcs.length > 0 && ssrcs[0].ssrc;
            var secondarySsrc;

            var flows = SDPUtils.matchPrefix(
              mediaSection,
              'a=ssrc-group:FID'
            ).map(function(line) {
              var parts = line.substr(17).split(' ');
              return parts.map(function(part) {
                return parseInt(part, 10);
              });
            });
            if (
              flows.length > 0 &&
              flows[0].length > 1 &&
              flows[0][0] === primarySsrc
            ) {
              secondarySsrc = flows[0][1];
            }

            description.codecs.forEach(function(codec) {
              if (codec.name.toUpperCase() === 'RTX' && codec.parameters.apt) {
                var encParam = {
                  ssrc: primarySsrc,
                  codecPayloadType: parseInt(codec.parameters.apt, 10),
                };
                if (primarySsrc && secondarySsrc) {
                  encParam.rtx = { ssrc: secondarySsrc };
                }
                encodingParameters.push(encParam);
                if (hasRed) {
                  encParam = JSON.parse(JSON.stringify(encParam));
                  encParam.fec = {
                    ssrc: primarySsrc,
                    mechanism: hasUlpfec ? 'red+ulpfec' : 'red',
                  };
                  encodingParameters.push(encParam);
                }
              }
            });
            if (encodingParameters.length === 0 && primarySsrc) {
              encodingParameters.push({
                ssrc: primarySsrc,
              });
            }

            // we support both b=AS and b=TIAS but interpret AS as TIAS.
            var bandwidth = SDPUtils.matchPrefix(mediaSection, 'b=');
            if (bandwidth.length) {
              if (bandwidth[0].indexOf('b=TIAS:') === 0) {
                bandwidth = parseInt(bandwidth[0].substr(7), 10);
              } else if (bandwidth[0].indexOf('b=AS:') === 0) {
                // use formula from JSEP to convert b=AS to TIAS value.
                bandwidth =
                  parseInt(bandwidth[0].substr(5), 10) * 1000 * 0.95 -
                  50 * 40 * 8;
              } else {
                bandwidth = undefined;
              }
              encodingParameters.forEach(function(params) {
                params.maxBitrate = bandwidth;
              });
            }
            return encodingParameters;
          };

          // parses http://draft.ortc.org/#rtcrtcpparameters*
          SDPUtils.parseRtcpParameters = function(mediaSection) {
            var rtcpParameters = {};

            // Gets the first SSRC. Note tha with RTX there might be multiple
            // SSRCs.
            var remoteSsrc = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:')
              .map(function(line) {
                return SDPUtils.parseSsrcMedia(line);
              })
              .filter(function(obj) {
                return obj.attribute === 'cname';
              })[0];
            if (remoteSsrc) {
              rtcpParameters.cname = remoteSsrc.value;
              rtcpParameters.ssrc = remoteSsrc.ssrc;
            }

            // Edge uses the compound attribute instead of reducedSize
            // compound is !reducedSize
            var rsize = SDPUtils.matchPrefix(mediaSection, 'a=rtcp-rsize');
            rtcpParameters.reducedSize = rsize.length > 0;
            rtcpParameters.compound = rsize.length === 0;

            // parses the rtcp-mux attrіbute.
            // Note that Edge does not support unmuxed RTCP.
            var mux = SDPUtils.matchPrefix(mediaSection, 'a=rtcp-mux');
            rtcpParameters.mux = mux.length > 0;

            return rtcpParameters;
          };

          // parses either a=msid: or a=ssrc:... msid lines and returns
          // the id of the MediaStream and MediaStreamTrack.
          SDPUtils.parseMsid = function(mediaSection) {
            var parts;
            var spec = SDPUtils.matchPrefix(mediaSection, 'a=msid:');
            if (spec.length === 1) {
              parts = spec[0].substr(7).split(' ');
              return { stream: parts[0], track: parts[1] };
            }
            var planB = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:')
              .map(function(line) {
                return SDPUtils.parseSsrcMedia(line);
              })
              .filter(function(msidParts) {
                return msidParts.attribute === 'msid';
              });
            if (planB.length > 0) {
              parts = planB[0].value.split(' ');
              return { stream: parts[0], track: parts[1] };
            }
          };

          // SCTP
          // parses draft-ietf-mmusic-sctp-sdp-26 first and falls back
          // to draft-ietf-mmusic-sctp-sdp-05
          SDPUtils.parseSctpDescription = function(mediaSection) {
            var mline = SDPUtils.parseMLine(mediaSection);
            var maxSizeLine = SDPUtils.matchPrefix(
              mediaSection,
              'a=max-message-size:'
            );
            var maxMessageSize;
            if (maxSizeLine.length > 0) {
              maxMessageSize = parseInt(maxSizeLine[0].substr(19), 10);
            }
            if (isNaN(maxMessageSize)) {
              maxMessageSize = 65536;
            }
            var sctpPort = SDPUtils.matchPrefix(mediaSection, 'a=sctp-port:');
            if (sctpPort.length > 0) {
              return {
                port: parseInt(sctpPort[0].substr(12), 10),
                protocol: mline.fmt,
                maxMessageSize: maxMessageSize,
              };
            }
            var sctpMapLines = SDPUtils.matchPrefix(mediaSection, 'a=sctpmap:');
            if (sctpMapLines.length > 0) {
              var parts = SDPUtils.matchPrefix(mediaSection, 'a=sctpmap:')[0]
                .substr(10)
                .split(' ');
              return {
                port: parseInt(parts[0], 10),
                protocol: parts[1],
                maxMessageSize: maxMessageSize,
              };
            }
          };

          // SCTP
          // outputs the draft-ietf-mmusic-sctp-sdp-26 version that all browsers
          // support by now receiving in this format, unless we originally parsed
          // as the draft-ietf-mmusic-sctp-sdp-05 format (indicated by the m-line
          // protocol of DTLS/SCTP -- without UDP/ or TCP/)
          SDPUtils.writeSctpDescription = function(media, sctp) {
            var output = [];
            if (media.protocol !== 'DTLS/SCTP') {
              output = [
                'm=' +
                  media.kind +
                  ' 9 ' +
                  media.protocol +
                  ' ' +
                  sctp.protocol +
                  '\r\n',
                'c=IN IP4 0.0.0.0\r\n',
                'a=sctp-port:' + sctp.port + '\r\n',
              ];
            } else {
              output = [
                'm=' +
                  media.kind +
                  ' 9 ' +
                  media.protocol +
                  ' ' +
                  sctp.port +
                  '\r\n',
                'c=IN IP4 0.0.0.0\r\n',
                'a=sctpmap:' + sctp.port + ' ' + sctp.protocol + ' 65535\r\n',
              ];
            }
            if (sctp.maxMessageSize !== undefined) {
              output.push('a=max-message-size:' + sctp.maxMessageSize + '\r\n');
            }
            return output.join('');
          };

          // Generate a session ID for SDP.
          // https://tools.ietf.org/html/draft-ietf-rtcweb-jsep-20#section-5.2.1
          // recommends using a cryptographically random +ve 64-bit value
          // but right now this should be acceptable and within the right range
          SDPUtils.generateSessionId = function() {
            return Math.random()
              .toString()
              .substr(2, 21);
          };

          // Write boilder plate for start of SDP
          // sessId argument is optional - if not supplied it will
          // be generated randomly
          // sessVersion is optional and defaults to 2
          // sessUser is optional and defaults to 'thisisadapterortc'
          SDPUtils.writeSessionBoilerplate = function(
            sessId,
            sessVer,
            sessUser
          ) {
            var sessionId;
            var version = sessVer !== undefined ? sessVer : 2;
            if (sessId) {
              sessionId = sessId;
            } else {
              sessionId = SDPUtils.generateSessionId();
            }
            var user = sessUser || 'thisisadapterortc';
            // FIXME: sess-id should be an NTP timestamp.
            return (
              'v=0\r\n' +
              'o=' +
              user +
              ' ' +
              sessionId +
              ' ' +
              version +
              ' IN IP4 127.0.0.1\r\n' +
              's=-\r\n' +
              't=0 0\r\n'
            );
          };

          SDPUtils.writeMediaSection = function(
            transceiver,
            caps,
            type,
            stream
          ) {
            var sdp = SDPUtils.writeRtpDescription(transceiver.kind, caps);

            // Map ICE parameters (ufrag, pwd) to SDP.
            sdp += SDPUtils.writeIceParameters(
              transceiver.iceGatherer.getLocalParameters()
            );

            // Map DTLS parameters to SDP.
            sdp += SDPUtils.writeDtlsParameters(
              transceiver.dtlsTransport.getLocalParameters(),
              type === 'offer' ? 'actpass' : 'active'
            );

            sdp += 'a=mid:' + transceiver.mid + '\r\n';

            if (transceiver.direction) {
              sdp += 'a=' + transceiver.direction + '\r\n';
            } else if (transceiver.rtpSender && transceiver.rtpReceiver) {
              sdp += 'a=sendrecv\r\n';
            } else if (transceiver.rtpSender) {
              sdp += 'a=sendonly\r\n';
            } else if (transceiver.rtpReceiver) {
              sdp += 'a=recvonly\r\n';
            } else {
              sdp += 'a=inactive\r\n';
            }

            if (transceiver.rtpSender) {
              // spec.
              var msid =
                'msid:' +
                stream.id +
                ' ' +
                transceiver.rtpSender.track.id +
                '\r\n';
              sdp += 'a=' + msid;

              // for Chrome.
              sdp +=
                'a=ssrc:' +
                transceiver.sendEncodingParameters[0].ssrc +
                ' ' +
                msid;
              if (transceiver.sendEncodingParameters[0].rtx) {
                sdp +=
                  'a=ssrc:' +
                  transceiver.sendEncodingParameters[0].rtx.ssrc +
                  ' ' +
                  msid;
                sdp +=
                  'a=ssrc-group:FID ' +
                  transceiver.sendEncodingParameters[0].ssrc +
                  ' ' +
                  transceiver.sendEncodingParameters[0].rtx.ssrc +
                  '\r\n';
              }
            }
            // FIXME: this should be written by writeRtpDescription.
            sdp +=
              'a=ssrc:' +
              transceiver.sendEncodingParameters[0].ssrc +
              ' cname:' +
              SDPUtils.localCName +
              '\r\n';
            if (
              transceiver.rtpSender &&
              transceiver.sendEncodingParameters[0].rtx
            ) {
              sdp +=
                'a=ssrc:' +
                transceiver.sendEncodingParameters[0].rtx.ssrc +
                ' cname:' +
                SDPUtils.localCName +
                '\r\n';
            }
            return sdp;
          };

          // Gets the direction from the mediaSection or the sessionpart.
          SDPUtils.getDirection = function(mediaSection, sessionpart) {
            // Look for sendrecv, sendonly, recvonly, inactive, default to sendrecv.
            var lines = SDPUtils.splitLines(mediaSection);
            for (var i = 0; i < lines.length; i++) {
              switch (lines[i]) {
                case 'a=sendrecv':
                case 'a=sendonly':
                case 'a=recvonly':
                case 'a=inactive':
                  return lines[i].substr(2);
                default:
                // FIXME: What should happen here?
              }
            }
            if (sessionpart) {
              return SDPUtils.getDirection(sessionpart);
            }
            return 'sendrecv';
          };

          SDPUtils.getKind = function(mediaSection) {
            var lines = SDPUtils.splitLines(mediaSection);
            var mline = lines[0].split(' ');
            return mline[0].substr(2);
          };

          SDPUtils.isRejected = function(mediaSection) {
            return mediaSection.split(' ', 2)[1] === '0';
          };

          SDPUtils.parseMLine = function(mediaSection) {
            var lines = SDPUtils.splitLines(mediaSection);
            var parts = lines[0].substr(2).split(' ');
            return {
              kind: parts[0],
              port: parseInt(parts[1], 10),
              protocol: parts[2],
              fmt: parts.slice(3).join(' '),
            };
          };

          SDPUtils.parseOLine = function(mediaSection) {
            var line = SDPUtils.matchPrefix(mediaSection, 'o=')[0];
            var parts = line.substr(2).split(' ');
            return {
              username: parts[0],
              sessionId: parts[1],
              sessionVersion: parseInt(parts[2], 10),
              netType: parts[3],
              addressType: parts[4],
              address: parts[5],
            };
          };

          // a very naive interpretation of a valid SDP.
          SDPUtils.isValidSDP = function(blob) {
            if (typeof blob !== 'string' || blob.length === 0) {
              return false;
            }
            var lines = SDPUtils.splitLines(blob);
            for (var i = 0; i < lines.length; i++) {
              if (lines[i].length < 2 || lines[i].charAt(1) !== '=') {
                return false;
              }
              // TODO: check the modifier a bit more.
            }
            return true;
          };

          // Expose public methods.
          if (typeof module === 'object') {
            module.exports = SDPUtils;
          }
        },
        {},
      ],
    },
    {},
    [1]
  )(1);
});
